From 0404d430dcee22ed2788c1e33108024a4353d0c6 Mon Sep 17 00:00:00 2001 From: iikorni Date: Sat, 22 Aug 2026 10:00:49 -0500 Subject: [PATCH] Begin converting QS engine to CPP --- .gitignore | 1 + .idea/.gitignore | 0 .idea/.name | 1 + .idea/editor.xml | 101 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/quakespasm-0.96.3.iml | 2 + .idea/vcs.xml | 6 + .idea/workspace.xml | 142 + CMakeLists.txt | 18 + LICENSE.txt | 339 ++ Misc/QuakeSpasm_512.png | Bin 0 -> 51125 bytes Misc/fitzquake080.txt | 417 ++ Misc/fitzquake080sdl.txt | 89 + Misc/fitzquake085.txt | 532 +++ Misc/fix_externaltex.patch | 113 + Misc/fs_search_order.patch | 34 + Misc/mk_header.c | 102 + Misc/qs_pak/Makefile | 18 + Misc/qs_pak/default.cfg | 118 + Misc/qs_pak/default.cfg.diff | 76 + Misc/qs_pak/gfx/conback.lmp | Bin 0 -> 327688 bytes Misc/qs_pak/maps/e1m1.diff | 18 + Misc/qs_pak/maps/e1m1@c49d.ent | 1970 ++++++++++ Misc/qs_pak/maps/e1m2.diff | 28 + Misc/qs_pak/maps/e1m2@0caa.ent | 3019 +++++++++++++++ Misc/qs_pak/maps/e1m4.diff | 30 + Misc/qs_pak/maps/e1m4@958e.ent | 3171 +++++++++++++++ Misc/qs_pak/maps/e2m2.diff | 54 + Misc/qs_pak/maps/e2m2@fbfe.ent | 2005 ++++++++++ Misc/qs_pak/maps/e2m3.diff | 74 + Misc/qs_pak/maps/e2m3@237a.ent | 2865 ++++++++++++++ Misc/qs_pak/maps/e2m7.diff | 22 + Misc/qs_pak/maps/e2m7@10a8.ent | 3633 +++++++++++++++++ Misc/qs_pak/mkpak.sh | 135 + Misc/systest.c | 256 ++ Quake/anorm_dots.hpp | 580 +++ Quake/anorms.hpp | 182 + Quake/arch_def.hpp | 178 + Quake/bgmusic.cpp | 478 +++ Quake/bgmusic.hpp | 43 + Quake/bspfile.hpp | 421 ++ Quake/cd_null.cpp | 53 + Quake/cd_sdl.cpp | 593 +++ Quake/cdaudio.hpp | 35 + Quake/cfgfile.cpp | 175 + Quake/cfgfile.hpp | 42 + Quake/chase.cpp | 118 + Quake/cl_demo.cpp | 503 +++ Quake/cl_input.cpp | 454 +++ Quake/cl_main.cpp | 841 ++++ Quake/cl_parse.cpp | 1260 ++++++ Quake/cl_tent.cpp | 361 ++ Quake/client.hpp | 379 ++ Quake/cmd.cpp | 869 +++++ Quake/cmd.hpp | 127 + Quake/common.cpp | 3022 +++++++++++++++ Quake/common.hpp | 412 ++ Quake/console.cpp | 1315 +++++++ Quake/console.hpp | 69 + Quake/crc.cpp | 94 + Quake/crc.hpp | 33 + Quake/cvar.cpp | 560 +++ Quake/cvar.hpp | 146 + Quake/default_cfg.hpp | 92 + Quake/draw.hpp | 48 + Quake/filenames.hpp | 193 + Quake/gl_draw.cpp | 772 ++++ Quake/gl_fog.cpp | 427 ++ Quake/gl_mesh.cpp | 603 +++ Quake/gl_model.cpp | 3094 +++++++++++++++ Quake/gl_model.hpp | 520 +++ Quake/gl_refrag.cpp | 221 ++ Quake/gl_rlight.cpp | 421 ++ Quake/gl_rmain.cpp | 1133 ++++++ Quake/gl_rmisc.cpp | 645 ++++ Quake/gl_screen.cpp | 1116 ++++++ Quake/gl_sky.cpp | 1134 ++++++ Quake/gl_texmgr.cpp | 1551 ++++++++ Quake/gl_texmgr.hpp | 108 + Quake/gl_vidsdl.cpp | 2392 ++++++++++++ Quake/gl_warp.cpp | 277 ++ Quake/gl_warp_sin.hpp | 84 + Quake/glquake.hpp | 439 +++ Quake/host.cpp | 947 +++++ Quake/host_cmd.cpp | 2364 +++++++++++ Quake/image.cpp | 640 +++ Quake/image.hpp | 38 + Quake/in_sdl.cpp | 1160 ++++++ Quake/input.hpp | 56 + Quake/keys.cpp | 1239 ++++++ Quake/keys.hpp | 201 + Quake/lodepng.cpp | 6665 ++++++++++++++++++++++++++++++++ Quake/lodepng.hpp | 2085 ++++++++++ Quake/main_sdl.cpp | 157 + Quake/mathlib.cpp | 474 +++ Quake/mathlib.hpp | 123 + Quake/menu.cpp | 2794 +++++++++++++ Quake/menu.hpp | 75 + Quake/miniz.cpp | 2282 +++++++++++ Quake/miniz.hpp | 726 ++++ Quake/modelgen.hpp | 141 + Quake/net.hpp | 115 + Quake/net_bsd.cpp | 96 + Quake/net_defs.hpp | 260 ++ Quake/net_dgrm.cpp | 1421 +++++++ Quake/net_dgrm.hpp | 39 + Quake/net_loop.cpp | 250 ++ Quake/net_loop.hpp | 40 + Quake/net_main.cpp | 906 +++++ Quake/net_sys.hpp | 200 + Quake/net_udp.cpp | 480 +++ Quake/net_udp.hpp | 45 + Quake/net_win.cpp | 121 + Quake/net_wins.cpp | 544 +++ Quake/net_wins.hpp | 45 + Quake/net_wipx.cpp | 448 +++ Quake/net_wipx.hpp | 45 + Quake/pl_linux.cpp | 98 + Quake/pl_osx.m | 99 + Quake/pl_win.cpp | 114 + Quake/platform.hpp | 48 + Quake/pr_cmds.cpp | 1858 +++++++++ Quake/pr_comp.hpp | 197 + Quake/pr_edict.cpp | 1401 +++++++ Quake/pr_exec.cpp | 653 ++++ Quake/progdefs.hpp | 28 + Quake/progdefs.q1 | 143 + Quake/progs.hpp | 154 + Quake/protocol.hpp | 279 ++ Quake/q_ctype.hpp | 99 + Quake/q_sound.hpp | 190 + Quake/q_stdinc.hpp | 252 ++ Quake/qs_bmp.hpp | 189 + Quake/quakedef.hpp | 336 ++ Quake/quakespasm.pak | Bin 0 -> 558464 bytes Quake/r_alias.cpp | 1006 +++++ Quake/r_brush.cpp | 1028 +++++ Quake/r_part.cpp | 1020 +++++ Quake/r_sprite.cpp | 196 + Quake/r_world.cpp | 1329 +++++++ Quake/render.hpp | 176 + Quake/resource.hpp | 26 + Quake/sbar.cpp | 1336 +++++++ Quake/sbar.hpp | 46 + Quake/screen.hpp | 84 + Quake/server.hpp | 247 ++ Quake/snd_codec.cpp | 331 ++ Quake/snd_codec.hpp | 107 + Quake/snd_codeci.hpp | 57 + Quake/snd_dma.cpp | 1065 +++++ Quake/snd_flac.cpp | 390 ++ Quake/snd_flac.hpp | 13 + Quake/snd_mem.cpp | 360 ++ Quake/snd_mikmod.cpp | 221 ++ Quake/snd_mikmod.hpp | 13 + Quake/snd_mix.cpp | 527 +++ Quake/snd_modplug.cpp | 125 + Quake/snd_modplug.hpp | 12 + Quake/snd_mp3.cpp | 461 +++ Quake/snd_mp3.hpp | 14 + Quake/snd_mp3tag.cpp | 405 ++ Quake/snd_mpg123.cpp | 223 ++ Quake/snd_opus.cpp | 211 + Quake/snd_opus.hpp | 13 + Quake/snd_sdl.cpp | 212 + Quake/snd_umx.cpp | 413 ++ Quake/snd_umx.hpp | 12 + Quake/snd_vorbis.cpp | 205 + Quake/snd_vorbis.hpp | 13 + Quake/snd_wave.cpp | 275 ++ Quake/snd_wave.hpp | 13 + Quake/snd_xmp.cpp | 187 + Quake/snd_xmp.hpp | 12 + Quake/spritegn.hpp | 117 + Quake/stb_image_write.hpp | 684 ++++ Quake/strl_fn.hpp | 16 + Quake/strlcat.cpp | 59 + Quake/strlcpy.cpp | 54 + Quake/sv_main.cpp | 1620 ++++++++ Quake/sv_move.cpp | 419 ++ Quake/sv_phys.cpp | 1233 ++++++ Quake/sv_user.cpp | 627 +++ Quake/sys.hpp | 73 + Quake/sys_sdl_unix.cpp | 530 +++ Quake/sys_sdl_win.cpp | 443 +++ Quake/vid.hpp | 93 + Quake/view.cpp | 975 +++++ Quake/view.hpp | 40 + Quake/wad.cpp | 167 + Quake/wad.hpp | 82 + Quake/world.cpp | 937 +++++ Quake/world.hpp | 87 + Quake/wsaerror.hpp | 91 + Quake/zone.cpp | 989 +++++ Quake/zone.hpp | 140 + 196 files changed, 103237 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/editor.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/quakespasm-0.96.3.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 CMakeLists.txt create mode 100644 LICENSE.txt create mode 100644 Misc/QuakeSpasm_512.png create mode 100644 Misc/fitzquake080.txt create mode 100644 Misc/fitzquake080sdl.txt create mode 100644 Misc/fitzquake085.txt create mode 100644 Misc/fix_externaltex.patch create mode 100644 Misc/fs_search_order.patch create mode 100644 Misc/mk_header.c create mode 100644 Misc/qs_pak/Makefile create mode 100644 Misc/qs_pak/default.cfg create mode 100644 Misc/qs_pak/default.cfg.diff create mode 100644 Misc/qs_pak/gfx/conback.lmp create mode 100644 Misc/qs_pak/maps/e1m1.diff create mode 100644 Misc/qs_pak/maps/e1m1@c49d.ent create mode 100644 Misc/qs_pak/maps/e1m2.diff create mode 100644 Misc/qs_pak/maps/e1m2@0caa.ent create mode 100644 Misc/qs_pak/maps/e1m4.diff create mode 100644 Misc/qs_pak/maps/e1m4@958e.ent create mode 100644 Misc/qs_pak/maps/e2m2.diff create mode 100644 Misc/qs_pak/maps/e2m2@fbfe.ent create mode 100644 Misc/qs_pak/maps/e2m3.diff create mode 100644 Misc/qs_pak/maps/e2m3@237a.ent create mode 100644 Misc/qs_pak/maps/e2m7.diff create mode 100644 Misc/qs_pak/maps/e2m7@10a8.ent create mode 100755 Misc/qs_pak/mkpak.sh create mode 100644 Misc/systest.c create mode 100644 Quake/anorm_dots.hpp create mode 100644 Quake/anorms.hpp create mode 100644 Quake/arch_def.hpp create mode 100644 Quake/bgmusic.cpp create mode 100644 Quake/bgmusic.hpp create mode 100644 Quake/bspfile.hpp create mode 100644 Quake/cd_null.cpp create mode 100644 Quake/cd_sdl.cpp create mode 100644 Quake/cdaudio.hpp create mode 100644 Quake/cfgfile.cpp create mode 100644 Quake/cfgfile.hpp create mode 100644 Quake/chase.cpp create mode 100644 Quake/cl_demo.cpp create mode 100644 Quake/cl_input.cpp create mode 100644 Quake/cl_main.cpp create mode 100644 Quake/cl_parse.cpp create mode 100644 Quake/cl_tent.cpp create mode 100644 Quake/client.hpp create mode 100644 Quake/cmd.cpp create mode 100644 Quake/cmd.hpp create mode 100644 Quake/common.cpp create mode 100644 Quake/common.hpp create mode 100644 Quake/console.cpp create mode 100644 Quake/console.hpp create mode 100644 Quake/crc.cpp create mode 100644 Quake/crc.hpp create mode 100644 Quake/cvar.cpp create mode 100644 Quake/cvar.hpp create mode 100644 Quake/default_cfg.hpp create mode 100644 Quake/draw.hpp create mode 100644 Quake/filenames.hpp create mode 100644 Quake/gl_draw.cpp create mode 100644 Quake/gl_fog.cpp create mode 100644 Quake/gl_mesh.cpp create mode 100644 Quake/gl_model.cpp create mode 100644 Quake/gl_model.hpp create mode 100644 Quake/gl_refrag.cpp create mode 100644 Quake/gl_rlight.cpp create mode 100644 Quake/gl_rmain.cpp create mode 100644 Quake/gl_rmisc.cpp create mode 100644 Quake/gl_screen.cpp create mode 100644 Quake/gl_sky.cpp create mode 100644 Quake/gl_texmgr.cpp create mode 100644 Quake/gl_texmgr.hpp create mode 100644 Quake/gl_vidsdl.cpp create mode 100644 Quake/gl_warp.cpp create mode 100644 Quake/gl_warp_sin.hpp create mode 100644 Quake/glquake.hpp create mode 100644 Quake/host.cpp create mode 100644 Quake/host_cmd.cpp create mode 100644 Quake/image.cpp create mode 100644 Quake/image.hpp create mode 100644 Quake/in_sdl.cpp create mode 100644 Quake/input.hpp create mode 100644 Quake/keys.cpp create mode 100644 Quake/keys.hpp create mode 100644 Quake/lodepng.cpp create mode 100644 Quake/lodepng.hpp create mode 100644 Quake/main_sdl.cpp create mode 100644 Quake/mathlib.cpp create mode 100644 Quake/mathlib.hpp create mode 100644 Quake/menu.cpp create mode 100644 Quake/menu.hpp create mode 100644 Quake/miniz.cpp create mode 100644 Quake/miniz.hpp create mode 100644 Quake/modelgen.hpp create mode 100644 Quake/net.hpp create mode 100644 Quake/net_bsd.cpp create mode 100644 Quake/net_defs.hpp create mode 100644 Quake/net_dgrm.cpp create mode 100644 Quake/net_dgrm.hpp create mode 100644 Quake/net_loop.cpp create mode 100644 Quake/net_loop.hpp create mode 100644 Quake/net_main.cpp create mode 100644 Quake/net_sys.hpp create mode 100644 Quake/net_udp.cpp create mode 100644 Quake/net_udp.hpp create mode 100644 Quake/net_win.cpp create mode 100644 Quake/net_wins.cpp create mode 100644 Quake/net_wins.hpp create mode 100644 Quake/net_wipx.cpp create mode 100644 Quake/net_wipx.hpp create mode 100644 Quake/pl_linux.cpp create mode 100644 Quake/pl_osx.m create mode 100644 Quake/pl_win.cpp create mode 100644 Quake/platform.hpp create mode 100644 Quake/pr_cmds.cpp create mode 100644 Quake/pr_comp.hpp create mode 100644 Quake/pr_edict.cpp create mode 100644 Quake/pr_exec.cpp create mode 100644 Quake/progdefs.hpp create mode 100644 Quake/progdefs.q1 create mode 100644 Quake/progs.hpp create mode 100644 Quake/protocol.hpp create mode 100644 Quake/q_ctype.hpp create mode 100644 Quake/q_sound.hpp create mode 100644 Quake/q_stdinc.hpp create mode 100644 Quake/qs_bmp.hpp create mode 100644 Quake/quakedef.hpp create mode 100644 Quake/quakespasm.pak create mode 100644 Quake/r_alias.cpp create mode 100644 Quake/r_brush.cpp create mode 100644 Quake/r_part.cpp create mode 100644 Quake/r_sprite.cpp create mode 100644 Quake/r_world.cpp create mode 100644 Quake/render.hpp create mode 100644 Quake/resource.hpp create mode 100644 Quake/sbar.cpp create mode 100644 Quake/sbar.hpp create mode 100644 Quake/screen.hpp create mode 100644 Quake/server.hpp create mode 100644 Quake/snd_codec.cpp create mode 100644 Quake/snd_codec.hpp create mode 100644 Quake/snd_codeci.hpp create mode 100644 Quake/snd_dma.cpp create mode 100644 Quake/snd_flac.cpp create mode 100644 Quake/snd_flac.hpp create mode 100644 Quake/snd_mem.cpp create mode 100644 Quake/snd_mikmod.cpp create mode 100644 Quake/snd_mikmod.hpp create mode 100644 Quake/snd_mix.cpp create mode 100644 Quake/snd_modplug.cpp create mode 100644 Quake/snd_modplug.hpp create mode 100644 Quake/snd_mp3.cpp create mode 100644 Quake/snd_mp3.hpp create mode 100644 Quake/snd_mp3tag.cpp create mode 100644 Quake/snd_mpg123.cpp create mode 100644 Quake/snd_opus.cpp create mode 100644 Quake/snd_opus.hpp create mode 100644 Quake/snd_sdl.cpp create mode 100644 Quake/snd_umx.cpp create mode 100644 Quake/snd_umx.hpp create mode 100644 Quake/snd_vorbis.cpp create mode 100644 Quake/snd_vorbis.hpp create mode 100644 Quake/snd_wave.cpp create mode 100644 Quake/snd_wave.hpp create mode 100644 Quake/snd_xmp.cpp create mode 100644 Quake/snd_xmp.hpp create mode 100644 Quake/spritegn.hpp create mode 100644 Quake/stb_image_write.hpp create mode 100644 Quake/strl_fn.hpp create mode 100644 Quake/strlcat.cpp create mode 100644 Quake/strlcpy.cpp create mode 100644 Quake/sv_main.cpp create mode 100644 Quake/sv_move.cpp create mode 100644 Quake/sv_phys.cpp create mode 100644 Quake/sv_user.cpp create mode 100644 Quake/sys.hpp create mode 100644 Quake/sys_sdl_unix.cpp create mode 100644 Quake/sys_sdl_win.cpp create mode 100644 Quake/vid.hpp create mode 100644 Quake/view.cpp create mode 100644 Quake/view.hpp create mode 100644 Quake/wad.cpp create mode 100644 Quake/wad.hpp create mode 100644 Quake/world.cpp create mode 100644 Quake/world.hpp create mode 100644 Quake/wsaerror.hpp create mode 100644 Quake/zone.cpp create mode 100644 Quake/zone.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17551f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cmake-build-debug/** \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..b5f4406 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +quakespasm \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..ca29dcc --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,101 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e4846d5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/quakespasm-0.96.3.iml b/.idea/quakespasm-0.96.3.iml new file mode 100644 index 0000000..4c94235 --- /dev/null +++ b/.idea/quakespasm-0.96.3.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..38983c8 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1787368285525 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4ac2018 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 4.3) +project(quakespasm) + +set(CMAKE_CXX_STANDARD 26) + +find_package(SDL) +find_package(SDL_net) +find_package(OpenGL) + +add_executable(quakespasm Quake/bgmusic.cpp Quake/cd_sdl.cpp Quake/cfgfile.cpp Quake/chase.cpp Quake/cl_demo.cpp Quake/cl_input.cpp Quake/cl_main.cpp Quake/cl_parse.cpp Quake/cl_tent.cpp Quake/cmd.cpp Quake/common.cpp Quake/console.cpp Quake/crc.cpp Quake/cvar.cpp Quake/gl_draw.cpp Quake/gl_fog.cpp Quake/gl_mesh.cpp Quake/gl_model.cpp Quake/gl_refrag.cpp Quake/gl_rlight.cpp Quake/gl_rmain.cpp Quake/gl_rmisc.cpp Quake/gl_screen.cpp Quake/gl_sky.cpp Quake/gl_texmgr.cpp Quake/gl_vidsdl.cpp Quake/gl_warp.cpp Quake/host.cpp Quake/host_cmd.cpp Quake/image.cpp Quake/in_sdl.cpp Quake/keys.cpp Quake/main_sdl.cpp Quake/mathlib.cpp Quake/menu.cpp Quake/miniz.cpp Quake/net_bsd.cpp Quake/net_dgrm.cpp Quake/net_loop.cpp Quake/net_main.cpp Quake/net_udp.cpp Quake/pl_linux.cpp Quake/pr_cmds.cpp Quake/pr_edict.cpp Quake/pr_exec.cpp Quake/r_alias.cpp Quake/r_brush.cpp Quake/r_part.cpp Quake/r_sprite.cpp Quake/r_world.cpp Quake/sbar.cpp Quake/snd_codec.cpp Quake/snd_dma.cpp Quake/snd_flac.cpp Quake/snd_mem.cpp Quake/snd_mikmod.cpp Quake/snd_mix.cpp Quake/snd_modplug.cpp Quake/snd_mp3.cpp Quake/snd_mp3tag.cpp Quake/snd_mpg123.cpp Quake/snd_opus.cpp Quake/snd_sdl.cpp Quake/snd_umx.cpp Quake/snd_vorbis.cpp Quake/snd_wave.cpp Quake/snd_xmp.cpp Quake/strlcat.cpp Quake/strlcpy.cpp Quake/sv_main.cpp Quake/sv_move.cpp Quake/sv_phys.cpp Quake/sv_user.cpp Quake/sys_sdl_unix.cpp Quake/view.cpp Quake/wad.cpp Quake/world.cpp Quake/zone.cpp ) + +target_link_libraries(quakespasm OpenGL::GL) +target_link_libraries(quakespasm SDL::SDL) +target_link_libraries(quakespasm vorbisfile) +target_link_libraries(quakespasm vorbis) +target_link_libraries(quakespasm ogg) +target_link_libraries(quakespasm mad) +target_link_libraries(quakespasm m) \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Misc/QuakeSpasm_512.png b/Misc/QuakeSpasm_512.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4d01a4d1e70f495c12538faca75fc1e3616e1a GIT binary patch literal 51125 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

X7ixSAv;7_Aa5mDFutRTRh3AH8&u zLpDZ$MXDChWVlCnWBVyE7ucFjbRs{GUQ+cecTYIvJ?o)t=< zzB7sbJec>10i_yZa<(aL12Nm3w8C!k5&MUS?d`|woHI-J!Q+g)tgFZ5Z}K8^n6_hJ zpiJi&Pe~0Uykt+^RBllDAF9|r&^mR2)lm0yjy@w_YN&#Ez#B5ZTi&5H8raJGb(mSM zn&OWGvJ#Zpp&pi9{I&#jQue{rg? zo9>W!^S^!&Ql9>lWCQe`w?E98Xu~REOm^;fV&kNE{`)s1%!GNy1`Uid^$F^udwzF| zU=IH8<5VX^F*MJDk0VK%8n!+_5d(>jubb&uxaMA^i=m<{JU0%+y)ihsqw$Ls7eLk9?LuX1L`-nyrIFXI<2@N=KLPF-vs6v3osSYO+I0&rGJ`DY6xApLu;QT#o zgv!6@dtiH&m?!-#F*xDjo}5Ex!g&(#*+}fA0B#>rtuhhe_v|bhb4`(d4r{^>hsH)0 ztup6fO;4Kz7OE|#0c={J`F|t%^i})P55NbUO9#r`F5!L~9XSO5ZxOc`Rqu#wTtP-8 zP?5#EhdNmV0HC6VnwXSlI70qm7CcX`2s`Yo*S1l~HnS{F2}GepJ+Ue^wgcoXPfia# zL5d1bOUe3~&X9jd(eulDEH~2k>$gv*2vhiA-&7HVRZ~*dXWA%y2M7RoAt-|+JrWP=@2@8F)$}3A|6mfm z>W)h{n?Zj)^5t#=9x*Cz@-ZHk*wC-yAa3i@xw9?B5VYh&X!Q;D_)bV43zr#7K(Ip65Zd)O;qf3-)t-((a!T zRC@B&@d&Uy{_48^dNqH8`hyyWdkhDG=bgS!G5Rgp$EVmL;KV^iqSE&Be@0uMh4r$@ RI|Uekz|+;wWt~$(696nj(v|=K literal 0 HcmV?d00001 diff --git a/Misc/fitzquake080.txt b/Misc/fitzquake080.txt new file mode 100644 index 0000000..025f250 --- /dev/null +++ b/Misc/fitzquake080.txt @@ -0,0 +1,417 @@ + +================================================================================ + +Fitzquake version 0.80, May 26, 2005 + +Filename : fitzquake080.exe +Author : John Fitzgibbons +Email Address : johnfitz@u.washington.edu +Author's Homepage : http://www.celephais.net/ +Fitzquake Homepage : http://www.celephais.net/fitzquake + +Fitzquake is a modified glquake based on the source code released by id +Software. My primary focus is fixing a lot of the rendering bugs which made +glquake inferior to the software renderer. My secondary focus is adding +conveniences for mappers and general users. I am also slowly adding support for +new modding or mapping features such as skyboxes, fog, and colored light. + +While I have made extensive changes to the code, I pretty much use the same +OpenGL features that the original glquake uses. Therefore, if you can run +glquake, you can probably run Fitzquake. + +I am not finished working on this project, so bug reports and feature requests +are welcome. + +Acknowlegements +-------------------------------------------------------------------------------- + +id Software (quake and quake2 code) +LordHavoc (code and assistance) +Bengt Jardrup (feedback, assistance, testing) + +additional thanks to: Aardappel, SmallPileOfGibs, FrikaC, Vondur, Topaz, Tomaz, +Tonik, Radix, EvilTypeGuy, NightBringer, MH, Maddes, Fett, Craig Wills, Heffo, +Riot, Gleeb, people in #flipcode, and others for their tutorials, code, testing, +and assistance. + +Copyright / Permissions +-------------------------------------------------------------------------------- + +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place - Suite 330, Boston, MA 02111-1307, USA. + +History +================================================================================ + +changes in 0.80 +--------------- + + - map loading is about four times faster. This is due to some optimizations in the texture loading code. + - video mode can now be changed in-game. You can change resolution, color depth, refresh rate, and switch between windowed and fullscreen modes. This can be accomplished easily using the video options menu, and is also available at the console using cvars. Note: if you launch fitzquake with -width, -height, -window, or -bpp in the command line, Fitzquake will use the command line settings and ignore whatever it reads from the config files at startup. However, you will still be able to change the video mode from the menu or the console as you like. Also note: to change video modes by execing a config file, the config file must include "vid_restart" after the other vid_ cvars have been set. (see cvars "vid_width," "vid_height," "vid_fullscreen," "vid_bpp," "vid_refreshrate," and commands "vid_restart," "vid_test") + - added control of entity count limits (MAX_EDICTS.) The default maximum has been increased from 600 to 1024. It can be raised even higher (up to 8192 -- this is a limit set by the network protocol) using a cvar. (see cvar "max_edicts") + - added control of vertical sync. (see cvar "vid_vsync") + - added control of anistropic filtering. Anisotropic filtering is a method to improve texture clarity on surfaces when viewed at steep angles. (see cvar "gl_texture_anisotropy") + - console buffer size can now be set. The command line option "-consize" allows you to specify the buffer size in kilobytes. For reference, fitzquake's default buffer size is 64k, and glquake's is 16k. 16k is also the smallest size fitzquake will allow you to set. Note: this buffer lives in the heap, so if you want to have a HUGE buffer, you might need to increase -heapsize also. + - increased max surface extents from 256 (the software quake maximum) to 2000. For reference, the glquake maximum is 512. This should eliminate the "bad surface extents" crash when running some maps that could be played in glquake, but not fitzquake or winquake. + - increased max vertices in an alias model from 1024 (the glquake maximum) to 2000 (the software quake maximum.) + - increased MAX_CHANNELS from 128 to 512 and MAX_DYNAMIC_CHANNELS from 8 to 128 + - screenshot filenames are now in the format "fitz0000.tga", increasing the maximum number of screenshots per folder from one hundred to ten thousand. + - vid_describemodes output cleaned up; now displays a list of valid refresh rates for each mode + - added a more intuitive cvar control of slow motion/fast-forward for demos and live gameplay. (see cvar "host_timescale") + - "reset to defaults" option in the menu now executes the "resetall" command before loading default.cfg + - game command now writes config.cfg to current gamedir before switching + - +mlook is now saved to config.cfg + - changed smallest allowed window size from 320x240 to 320x200 + - console cursor images are now hard-coded, and the insert mode cursor is now a vertical bar instead of an underscore. This was prompted by several popular mods (OUM, Xmen TC) using nonstandard cursors, which are incompatible the fitzquake console enhancements. + - gl_farclip now defaults to 16384. This should be high enough to handle even the largest open areas without unwanted clipping. The only reason you'd want to lower this number is if you see z-fighting. + - added cvar r_drawworld (see below) + - added cvar r_showtris (see below) + - added cvar r_showbboxes (see below) + - added a command to cycle a cvar through a list of values. (see command "cycle") + - mapname command now works on dedicated server. + - added a hack to fix those white edges on the bottom of the large box of shells. I feel dirty, but they look better, now. + - changed stuffcmds behavior to allow hyphenated map names, config file names, etc. in the command line. (example: "fitzquake.exe +exec my-config.cfg +map my-map") However, you still can't load a map or config file when the first character in the filename is a hyphen. + - skybox loader now reverts to scrolling sky if all 6 skybox faces failed to load. + - tweaked circular particles to match the apparent size of the square particles. + - fixed crash when loading mods with large player skins (like the chainsaw mod, and PerQuake.) Colormapping can now handle any size player texture (limited to the heapsize, of course.) + - fixed bug where a replacement model in a mod is messed up becuase a matching .ms2 file in id1/glquake is present. The fix is simply to disable all mesh caching, which slows down model loading a little. + - fixed freeze when gl_overbright is 1 and either texture_env_combine or multitexture is disabled or not supported. + - fixed "numgltextures == MAX_GLTEXTURES" crash when viewing multiplayer->setup menu. + - fixed "numgltextures == MAX_GLTEXTURES" crash when playing large maps (nesp09,) due to frequent model recaches. + - fixed bug where entities and water were not being drawn any frame in which a model had to be recached. + - fixed skybox texture showing up in the place of other textures/skins/lightmaps, when the previous loaded map had a skybox. + - fixed missing polygons on edges of screen when underwater and r_waterwarp is 1. + - fixed missing polygons on edges of screen when r_stereo is enabled. + - fixed long mapnames that scroll are truncated to be shorter than what appears in the console. Well, sort of. What I did was increase the maximum length from 39 to 127. A mapname longer than 127 will still be truncated, but these are pretty rare. + - fixed rogue's teamplay skin showing up as an all-white texture. + - fixed checkerboard texture shows up sometimes when an animated texture has fullbright pixels on some frames, but not others (reactor core in junk.bsp) + - fixed color 255 is not fullbright + - fixed fuscia dots appearing in corners of resampled textures + - fixed mapname command crashes when client is disconnected. + - fixed cl_nolerp 1 screws up speed of demo playback + - fixed annoying client-side step-up smoothing when in noclip mode (except on nonlocal servers.) + - fixed scr_clock 2 displays hour twelve as "0" instead of "12" + - fixed skip textured surfaces still drawn even after running tyrann's skip utility + - fixed crash when loading a map with a sky texture where the "sky" in the texturename is not lowercase + - fixed hang in e2m2 on easy skill, where you can shoot one of the buttons at the beginning and then trick-jump through the open gate. Now it prints a warning message ("SV_TouchLinks: null link") and lets you keep playing. + - fixed bug when viewsize < 100, and r_oldwater is 0, where you can see the water textures overlaying part of the brown frame around the viewport. + - fixed bug where imagelist and r_speeds2 would report the same megabyte counts in both 16bpp and 32bpp mode, even though texture bpp should match (and does match) framebuffer bpp. The numbers are now different and accurate for each bit depth. + - removed cvars "vid_config_x," "vid_config_y," "vid_wait," "vid_nopageflip," "_vid_wait_override," "_vid_default_mode," "_vid_default_mode_win," and "vid_stretch_by_2," none of which glquake ever used. + - removed cvar "vid_mode" and commands "vid_describemode" and "vid_nummodes," because of the new video mode handling. + +cvars: + + - gl_texture_anisotropy. Controls the amount of anisotropy in texture filtering. If 1 or less than 1, texture filtering is normal (isotropic.) If greater than 1, increasing degrees of anisotropic filtering are used, up to the hardware maximum. Set value to 2 for 2x anisotropic, 4 for 4x, etc. Default 1. + + - host_timescale. Scales the passage of time on client and server. Set to 0 or 1 for normal speed, less than 1 for slow motion, and greater than 1 for fast-forward. If greater than 0, overrides host_framerate. Default 0. + + - max_edicts. Sets the maximum number of entites on both the client and server. Default 1024. Acceptable values range from 256 to 8192. Set to 600 to mimic standard quake. Changes won't take effect until the next time a map is loaded. Note: if a client connects to a server, and the client's maximum is lower than the server's, the client will probably crash if it ever sees an entnum higher than its local max_edicts. Warning: you may need to increase -heapsize if you want a high max_edicts value. + + - r_drawworld. If 1, draw world as usual. If 0, don't draw the world. Default 1. (compare r_drawentities) + + - r_showbboxes. If 1, draw a wireframe bounding box around each entity. Note that these are the server-side per-edict physics bounding boxes, not the client-side per-model rendering bboxes. If 0, disable this feature. Default 0. + + - r_showtris. If 1, draw wireframe outlines for every triangle in the scene. Like in Quake 3, the lines will be visible even through solid geometry. If 2, draw the outlines only on visible surfaces (like r_showtris 2 in Medal of Honor.) If 0, disable wireframe overlay. Default 0. + + - vid_bpp. Sets the color depth of the screen in fullscreen mode. Windowed mode ignores this setting. Can be 16 or 32. Default 16. (Changes won't take effect until the next call to vid_restart.) + + - vid_fullscreen. If 1, fitzquake will run fullscreen. If 0, fitzquake will run in a window. Default 1. (Changes won't take effect until the next call to vid_restart.) + + - vid_height. Sets the vertical screen/window resolution. Default 480. In windowed mode, cannot be less than 200. (Changes won't take effect until the next call to vid_restart.) + + - vid_refreshrate. Sets the refresh rate of the screen in fullscreen mode. Windowed mode ignores this setting. Possible values include 60, 70, 72, 75, 85, etc. Default 60. (Changes won't take effect until the next call to vid_restart.) + + - vid_width. Sets the horizontal screen/window resolution. Default 640. In windowed mode, cannot be less than 320. (Changes won't take effect until the next call to vid_restart.) + + - vid_vsync. Set to 1 to enable vertical sync, which eliminates tearing, but caps your framerate at the monitor refreshrate. Set to 0 to disable vertical sync, which allows tearing but doesn't cap your framerate. Default 0. Note: If fitzquake detects that your driver settings have forced vsync to be disabled, it will post a message to the console saying so, and this cvar will have no effect. + +commands: + + - "cycle [ [ ...]]" to cycle a cvar through a list of values.. Note: this command will get stuck on a list that contains the same value more than once, such as "cycle blah 1 2 1 3". If you're doing anything that complex you can just use aliases. + + - vid_restart. Tries to set a video mode that matches the values of vid_width, vid_height, vid_fullscreen, and, if vid_fullscreen is 1, vid_bpp and vid_refreshrate. + + - vid_test. Like vid_restart, except that after switching to the new mode pops up a confirmation dialogue. This is useful if you are not sure what modes your monitor can handle, so you don't get stuck with a blank screen. The dialogue has a time limit so that if you don't press a key within 5 seconds, it will revert to the previous mode. + +changes in 0.75 +--------------- + + - totally rewritten bsp drawing code. The new code combines the advantages of the gl_texsort 1 and gl_texsort 0 codepaths from glquake into one codepath that uses texture sorting and multitexture. In my tests, i've found that it's about the same speed as glquake in low poly scenes (like the original quake levels,) but as you get into the thousands of wpolys, it's faster and faster. + - 2x overbright lighting. Lighting now looks like software quake. Just like overbright lighting on models, overbright on world polys requires GL_EXT_texture_env_combine (TNT and later, voodoo4 and later.) Without it, FitzQuake will be use two passes to render overbright world polys. So if you don't have that extension, you might disable it for performance reasons. (see cvar "gl_overbright") + - colored lighting support using .lit files. + - totally new water surface animation. The new method requires no surface subdivision, isn't plagued by tearing and sparklies caused by tjunctions, and looks almost identical to software quake's water, and doesn't slow down on very large sheets of water. (see cvars "r_waterquality" and "r_oldwater") + - old water aninmation fixed so that it doesn't look bad when gl_subdivide_size is 32 or lower. + - can load external textures (currently targa and pcx) if they match the texture name in the bsp and are located in the id1/textures, or /textures directory. At the moment no other images (skins, menu, etc.) can be replaced. + - gamma correction now goes back to normal when Fitzquake loses focus. + - tab completion now adds a space after the completed command/cvar if the cursor is at the end of the editline. + - increased the max length of the video mode list from 30 to 80. This should alleviate the problems people with newer cards were having trying to use some 32-bit modes -- there were so many 16-bit modes that they filled up the list before all 32-bit modes could be detected. The video menu will still only list a certain number of modes due to space constraints, but you can see the complete list by using the console command "vid_describemodes" + - number of listed video modes in the "video options" menu increased from 27 to 36. + - added alpha control for the front sky layer. (see cvar "r_skyalpha") + - clock can now display time in 24-hour or "military" time. + - added an "mtex" counter to the r_speeds 2 readout. This measures the number of megabytes of texture memory used each frame to draw the scene. Note: this doesn't count textures used to draw the console, menu, or statusbar. + - added optional drawing of surfaces inside sky leaves, for compiler/map testing purposes. (see cvar "r_oldskyleaf") + - sky surfaces on bmodels are now visible, though drawn incorrectly. + - fixed multiple textures in bsp with same name or no name get overwritten/not loaded. (example: rd1m3) + - fixed some nasty texture loading bugs that were especially hounding 3dfx users (wrong texture, no texture, or unnecessarily low-res textures displayed on models and world.) These bugs would also occur in conjunction with nonzero values of gl_max_size or gl_picmip. + - fixed bug where changing gl_max_size or gl_picmip in-game would screw up alias model texture coordinates. + - fixed bug where if -gamma is specified at the command line, the "gamma" cvar would be ignored. + - fixed model more than 2048 units above floor us unlit by static lighting. + - fixed crash with con_logcenterprint when centerprint message was too long. + - fixed crash when changing to a nonexistant gamedir and then trying to write a file (screenshot, etc.) Fitzquake now creates the directory as needed. + - fixed "bad surface extents" error when sky or water surface is missing from bsp file. + - fixed alias model shadows write to z-buffer. + - fixed underwater intermission camera has warp, but no screenblend. + - fixed console buffer still scrolled back after using the "clear" command. + - fixed console command history (the list of previous commands) not being rewound after toggling the console. + - removed cvar gl_texsort. New bsp drawing code always sorts by texture. + - removed cvar gl_ztrick. The depth buffer is now cleared every frame. + - removed cvar gl_keeptjunctions. Extra verts created by qbsp to eliminate tjunctions are now always kept. + - removed "sliding on monsters' heads" fix, becuase I decided I don't like the idea of changing gameplay, even if the original behaviour is clearly a bug. Since this bug can be fixed in quakec also, I feel safer leaving it as it was. + +cvars: + + - gl_overbright. default 1. This variable controls overbright lighting on the world polygons. (For lighting on models, use gl_overbright_models.) If 1, overbright will be enabled and lighting will resemble software Quake. If 0, overbright will be disabled and lighting will resemble GLQuake. + + - r_oldskyleaf. default 0. If 0, surfaces inside sky leaves will be skipped by the renderer. If 1, they will be drawn whenever they are in your PVS, just like any other surface. + + - r_oldwater. default 1. If 1, use the old GLQuake method of subdividing the water surface to enable a warping animation. If 0, use the new render-to-texture method. Note: in general, r_oldwater 0 looks better and runs slower. So experiment to see if the performance hit is acceptable to you. + + - r_skyalpha. default 1. Sets the alpha of the front sky layer. Note that if sky alpha is less than 1.0, the sky will be drawn in two passes even if you have multitexture. + + - r_waterquality. default 8. Sets the quality of the water when r_oldwater is 0. Can be anywhere from 1 to 64. Lower values give better performance, higher values look better. A value of 4 will provide water that looks at least as good as glquake can get, and 32 is close enough to software quake for all but the most picky. To control the quality of water when r_oldwater is 1, use gl_subdivide_size instead. + + - scr_clock. default 0. If 1, game time is displayed. If 2, system time is displayed in 12-hour format. If 3, system time will be displayed in 24-hour or "military" time. + +Changes in 0.70 +--------------- + + - added anaglyph stereo rendering. Note that this will cut your framerate in half, as it is rendering the scene once for each eye. You might want to turn off r_drawviewmodel as it is hard to focus on becuase it is so close. (see cvars "r_stereo", "r_stereodepth") + - now uses ARB_multitexture if present, otherwise tries to use SGIS_multitexture. This should fix various blending bugs on some cards, such as weird cloud layers, all-black models, and inverted torches. (only ARB guarantees that GL_DECAL blending will work) + - overbright models now uses GL_EXT_texture_env_combine if supported (TNT and later, voodoo4 and later,) which saves one or two passes on model rendering when gl_overbright_models is 1. The command line option -nocombine will disable this. + - gl_overbright_models now defaults to 1 + - custom palettes are now correctly loaded when you use the "game" command. + - RecursiveLightPoint is now lerped for smoother lighting of slow-moving models. Check out the ogre in e3m3 for an example. + - dynamic lighting (rockets, etc) has been moderately sped up. (some lightmaps were being uploaded even though they weren't actually touched by a dynamic light.) + - overhauled 2d drawing to allow independent scaling of console, menu, and sbar (see cvars "scr_conwdith," "scr_menuscale," and "scr_sbarscale") command line switches "-conwidth" and "-conheight" removed. cvar "scr_stretch_menus" removed. + - user control of max framerate. (see cvar "host_maxfps") + - improved the accuracy of the FPS counter a bit, but it still seems suspect. + - particles can now be drawn as quads or triangles. (see cvar "r_quadparticles") + - opengl information (vendor, renderer, version, extensions) is no longer printed during initialization. (see command "gl_info") + - rewrote texture management. Now instead of quake's memory usage going up and up forever (becuase textures were uploaded to the opengl.dll and never freed,) all textures will get flushed when you switch games, bringing you back down to where you were when fitzquake first launched. + - gl_texturemode command will now accept a number (1 through 6) as well as the name (gl_linear_mipmap_nearest, etc.) + - gl_describetexturemodes will list all texturemodes. + - the inside of sky leaves are no longer rendered -- so when noclipping inside them, it will look the same as noclipping inside a solid wall. This does not affect gameplay. + - cleaned up intermission display in singleplayer -- no more overlapping numbers + - r_speeds readout modified a bit. (see cvar "r_speeds") + - r_sky_quality now defaults to 12 instead of 8, since skies are now drawn in 1 pass (with multitexture at least) + - gl_texsort now always defaults to 0. (it used to be forced to 1 when multitexture was unavailable) + - fixed bug where models would actually darken when dlights got bright enough + - fixed deathmatch, coop cvars not reset to zero when starting a new game from the single player menu + - fixed r_lightmap doesn't work when gl_texsort = 0. + - fixed inverted lightmaps / no textures / no dynamic light bug when multitexture is disabled and gl_texsort = 0 + - fixed scrolling map title in wrong place when width does not equal conwidth + - fixed a few color borking problems in 16-bit mode. The front sky layer, sprites, and pics with transparency will still look a bit off (as in glquake,) but at least the console image and statusbar background look better. Though it isn't perfect, it should once again look like what you expect from glquake in 16bit. + - fixed pixel gap on both sides of the console in 1024x768 + - fixed wpoly count being slightly lower when gl_texsort = 0 (the count was correct when gl_texsort = 1) + - fixed lmap count always zero when gl_texsort = 1 + - dlight fans are now drawn after water, so that it looks right at least when wateralpha is 1. + - fixed a possible bug with older 3dfx cards (voodoo 1/2/rush) where the gamma cvar might not work (untested) + - fixed old commands showing up in the console prompt after hitting 'enter' + - increased MAX_HANDLES so that you should never see the "out of handles" error message. + - removed cvar "chase_alpha." the transparent player model was buggy and didn't work well in a lot of conditions. + - removed cvar "gl_doubleeys" (yes, that is the correct misspelling) With overbright models, eyes can be seen as easily as in software mode. + - removed cvars "cl_crossx," "cl_crossy," "lcd_x," "lcd_yaw," and "gl_reporttjunctions" which didn't do anything (in glquake, at least). + - removed support for GL_EXT_shared_texture_palette (special 8-bit texture format) + - removed VCR code. command line switches "-record" and "-playback" no longer supported. + - removed support for command line switch "-gamma" -- just use the gamma cvar, or idgamma or something. + +cvars: + + - r_stereo. default 0. If nonzero, the scene will be rendered once in red, and again in cyan, with the two views shifted slightly. If you have 3D glasses you can appreciate this (assumes that the left eye is red and the right eye is cyan.) The value of r_stereo sets the eye separation. If your glasses have red on the right eye, use a negative value to flip the views. + + - r_stereodepth. default 128. Sets the distance at which the two views will converge when stereo rendering is active. + + - scr_conwdith. default 0. Sets the virtual console width, where smaller numbers means larger text. Values larger than window width, or smaller than 320, will be clamped to that range, and all values will be rounded to a multiple of 8. If 0, the window width will be used. Note that values that divide evenly into the window width will make the text look nicest. + + - scr_menuscale. default 1. Sets the scale factor for menus and other centered overlays. If 1, images will be drawn at 1:1 scale. If 2, images will be double size. Menus will never be drawn smaller than 1:1, and never larger than the size of the window. Note that integer values will make the text look nicest. + + - scr_sbarscale. default 1. Sets the scale factor for the statusbar. If 1, images will be drawn at 1:1 scale. If 2, images will be double size. The statusbar will never be drawn smaller than 1:1, and never larger than the width of the window. Note that integer values will make the text look nicest. + + - host_maxfps. default 72. sets the maximum frames per second fitzquake will render (also the maximum number of server frames per second.) Clamped to the range 10 - 1000. Set to 72 to mimic standard quake. + + - r_quadparticles. default 1. If 1, particles are drawn as GL_QUADS instead of GL_TRIANGLES. Quads use 4 verts instead of 3, but the fillrate cost is 1/2 that of triangles. Depending on your card, either one may be faster. This cvar has no effect on the appearance of particles. + + - r_speeds. default 0. Values of 1 and 2 will give you increasing amounts of information. When you see two numbers separated by a slash, the first number is polys, and the second number is passes. + +commands: + + - gl_info. Displays opengl info which was previously displayed during initialization: vendor, renderer, version, and extensions + + - imagelist. Displays a list of loaded textures, and their dimentions. + + - gl_texturemode. Now accepts a number (1 through 6) as well as the name (gl_nearest, etc.) + + - gl_describetexturemodes. Lists all texturemodes. + +Changes in 0.65 +--------------- + + - gamma cvar now supported. (see cvar "gamma") + - fullbrights on models now supported. + - odd-sized world textures are now bilinearly resampled (instead of glquake's nearest pixel resample) + - removed all fixed-size buffers for loading textures; now the only limit is the size of your memory heap (textures will still be downsampled if they are bigger than the hardware can handle) + - styled lights (torch flicker, etc.) can now be disabled (see cvar "r_flatlightstyles") + - sky now uses multitexture if available. + - centerprints are now optionally logged to the console (see cvar "con_logcenterprint") + - number of savegame slots increased to 20 (from 12) + - if a map title is longer than 22 characters, it scrolls marquee-style in the statusbar. + - gl_flashblend defaults to 1 again. (consistency with glquake) + - gl_ztrick defaults to 1 again. (consistency with glquake) + - command line gamma now defaults to 1.0 for all cards. (consistency with glquake, plus hardware gamma is better) + - now checks hardware for maximum texture size. Users of later voodoo cards should be able to see large textures now. (see cvar "gl_max_size") + - now shows AM/PM when scr_clock is 2 + - fixed crash when starting dedicated server + - fixed crash when loading too many textures. (now it throws a sys_error and quits) + - fixed fitzquake-specific crash when player goes near water in a demo playback. + - scr_conalpha now actually works. + - keypad enter in the console works again. + - optional 2x overbrightening on models. (see cvar "gl_overbright_models") + - optional quake2-style noclip. (see cvar "sv_altnoclip") + - new icon + +cvars: + + - con_logcenterprint: If 1, centerprint messages will be logged to the console in sp/coop. If 2, they will also be logged in deathmatch. Default 1. + + - gamma: Brighten or darken the screen to compensate for differences between monitors. This is now supported by using your video card's gamma support. Just like in winquake, values less than 1 are brighter and values greater than one are darker. Default 1. Notes: I have added special code so that this will work on 3dfx cards too, but i have no way to check that it works. If fitzquake crashes, your hardware gamma may be stuck at the wrong value. You can use the "display settings" control panel fix this, or you could try lordhavoc's useful "setgamma" utility (available on this page.) Also note that if texture-brightening gamma has been requested at the command line (fitzquake.exe -gamma ), harware gamma will not be used and the gamma cvar will have no effect. + + - gl_max_size: Now defaults to 0. If 0, textures will be as large as the hardware allows. Positive values can be used to impose a limit smaller than the hardware's reported maximum. + + - gl_overbright_models: If 1, models will be rendered using 2x overbrightening and will appear at roughly the same brightness as they do in software quake, which is noticably brighter than the default fitzquake / glquake appearance. I disabled this cvar by default becuase this is still a poorly supported feature -- it currently takes 2 or 3 passes to render a model when this feature is enabled, compared to only 1 pass when it is disabled. Default 0. + + - r_flatlightstyles: If 1, styled lights (torch flicker, etc.) will be displayed as a steady light. If 2, the peak intensity will be used instead of the average intensity. Default 0. + + - sv_altnoclip: If 1, enable the alternative noclip movement which resembles quake2 and is not constrained to the horizontal plane. Set to 0 to retain quake's original noclip behavior. Default 1. + +Changes in 0.60 +--------------- + + - graphics: + - better sky projection, configurable for performance + - menus and other overlays are now centered on the screen, and can optionally stretch to fit resolution. + - all 2d graphics now obey gl_texturemode and all texturemode changes take effect immediately + - underwater warping now resembles quake3's perspective munging, and obeys r_waterwarp + - fixed frustum culling b0rked (giving HOM at certain FOV / vidsize / screensize combinations) + - fixed lack of support for VP_PARALLEL_ORIENTED, VP_PARALLEL_UPRIGHT, and FACING_UPRIGHT sprites + - fixed fullbrights not displayed on world/bmodels + - fixed texture cache mismatch error + - fixed pink edges on sprites, menu items, etc + - fixed particle z-buffer bug (apparent when a particle is in front of a water surface) + - fixed ugly resampling of non-power of two gfx, skins, sprites + - fixed large models (shamblers, shub) dissapearing near edge of screen + - fixed statusbar not visible when gl_clear = 1 + - fixed r_fullbright change not immedate when gl_texsort = 0 + - fixed alias models still dark when r_fullbright = 1 + - fixed HOM when screen is partially underwater + - fixed gunshot decals not showing up on some walls + - sky and water warp now freeze when you pause, as well as lightning bolts + - r_novis changes now take effect immediately, rather than when you cross a leaf boundary + - removed mirror code + - two particle styles, circle and square (cvar controlled) + - console: + - improved tab completion: + - hitting tab once will display a list of possible matches and complete the line using the first match + - hitting tab or shift-tab will cycle through the matches + - autocomplete will now match against aliases as well as commands and cvars. + - autocomplete will now complete later in the string (e.g. "bind mouse1 +att" + tab will complete '+attack') + - autocomplete will even complete inside the string (e.g. "bind m +attack" + tab will complete 'mouse1' if the cursor is right after the 'm') + - new key functionality: + - tab -- autocomplete, cycle through multiple matches + - shift-tab -- cycle backwards through multiple matches + - ins -- toggle insertmode + - del -- delete current character + - backspace -- delete previous character + - ctrl-v -- paste from windows clippboard + - leftarrow -- move cursor one character left in line + - rightarrow -- move cursor one character right in line. or, get one character from the previous command. + - home -- move cursor to beginning of line + - end -- move cursor to end of line + - ctrl-pgup -- scroll up a screen at a time + - ctrl-pgdn -- scroll down a screen at a time + - ctrl-home -- scroll to top of console history + - ctrl-end -- scroll to bottom of console history + - carets indicate that you are scrolled back, like in quakeworld/quake2 + - left arrow, right arrow, pgup, pgdn keys now auto-repeat + - quadrupled the length of the console history + - commands: + - "game " to load a mod. + - "reset " to reset a cvar to default. Note that this is the engine default, not the default.cfg value + - resetall. resets all cvars. + - mods. lists all child folders of quake directory which contain either a progs.dat or a pak file + - maps. lists all addon levels available (i.e. all levels that are not in id1/*.pak) + - mapname. outputs the short name of the current level (e.g. "e1m5") + - cmdlist. alphabetized. 'cmdlist blah' will list only cmds that start with 'blah' + - cvarlist. alphabetized. 'cvarlist blah' will list only cvars that start with 'blah' + - "inc [amount]" to increment a cvar by amount. amount defaults to 1. + - "toggle " to invert the value of a cvar (nonzero -> 0 and 0 -> 1) + - god, noclip, notarget, and fly can now be explicitly set. example: "god 0" will disable god mode + - viewpos. outputs (X,Y,Z) pitch yaw roll + - "give a " now gives you armour. type depends on value + - bindlist. lists all key bindings + - "alias " now outputs the current command string + - "unalias " to delete an alias + - unaliasall. delete all aliases. + - condump. dumps console to condump.txt + - fog. set global fog. syntax is "fog ", "fog ", or "fog " See section on modding for details. Set density to 0 to disable fog. + - "sky " to load a skybox. if skyname is "", this will turn off skybox rendering. + - stuffcmds now parses the "cmdline" cvar rather than the args actually passed to the program. This is useful for loading mods dynamically, so you can edit the cmdline before execing quake.rc (which calls "stuffcmds") + - cvars: + - scr_stretch_menus. default 1. if 1, menus and other overlays are stretched to fill the screen + - scr_conalpha. default 1. This is the opacity when the console is halfscreen. 0.6 will mimic glquake + - scr_clock. default 0. if 1, game time is displayed. If 2, system time is displayed. + - scr_showfps. default 0. if 1, FPS are displayed. + - r_waterwarp recognized. default 1. if zero, no underwater warping will take place + - r_drawflat recognized. default 0. if 1, polygons will be drawn as a solid color with no lightmap or texture + - r_particles. default 1. 0 = no particles, 1 = circular particles, 2 = square particles + - r_fastsky. default 0. if 1, sky will be rendered as solid color, for added performance + - r_sky_quality. default 8. Higher number divides the sky more, for a smoother effect and slower performance. + - r_clearcolor now supported. default 2. specify a palette index from 0 to 255. + - gl_fullbrights. default 1. set to 0 to disable fullbrights + - gl_farclip. default 8192. set to 4096 to mimic glquake. note that the skybox will be drawn somewhat closer than this value. + - cl_maxpitch. default 90 (straight down.) set to 80 to mimic normal quake + - cl_minpitch. default -90 (straight up.) set to -70 to mimic normal quake + - cl_keypad. default 1. if 0, keypad keys will respond as in quake.exe (for example, pushing 'KP_END' will be the same as pushing '1') + - gl_flashblend now defaults to 0. + - gl_ztrick now defaults to 0. + - gl_keeptjunctions now defaults to 1. (note, contrary to the name of this cvar, what is being kept is the extra polygon verts which *eliminate* tjunctions. This is a good thing, so i made it default to 1) + - chase_alpha. default 1. lower values make the chasecam player model translucent. Buggy. + - keyboard + - keypad keys are now bindable: + KP_NUMLOCK, KP_SLASH, KP_STAR, KP_MINUS, KP_HOME, KP_UPARROW, KP_PGUP, KP_PLUS, KP_LEFTARROW, KP_5, KP_RIGHTARROW, KP_END, KP_DOWNARROW, KP_PGDN, KP_ENTER, KP_INS, KP_DEL + - command line: + - running with -gamma at the command line will set the gamma. There is still no way to change this value in game. Default is 1.0 for 3dfx cards, 0.7 for all others + - if unspecified, -conwidth now defaults to -width + - modding extensions: + - skyboxes (worldspawn and qc settable), currently only targa and pcx formats accepted + - global fog (worldspawn and qc settable) + - physics + - fixed sliding around while standing on solid entities' bounding boxes (monsters, players, etc) + - misc + - default heapsize is now 32mb (was 16mb) + - default zonesize is now 256k (was 48k) + diff --git a/Misc/fitzquake080sdl.txt b/Misc/fitzquake080sdl.txt new file mode 100644 index 0000000..5808c72 --- /dev/null +++ b/Misc/fitzquake080sdl.txt @@ -0,0 +1,89 @@ +================================================================================ + +Beta release of an SDL port of Fitzquake version 0.80, July 5, 2008 + +Author : Kristian Duske +Email Address : deceive.inveigle.obfuscate@gmail.com +Author's Homepage : http://www.kristianduske.com/fitzquake +Minimum SDL version : 1.2.10 + +This is a port of the Fitzquake engine to SDL. The main goal of this port is to +allow fitzquake to run on all major platforms. Currently there are builds for +Windows, Linux and Mac OS X. + +To run this engine, you need to install the SDL and SDL_net library binaries. For +Linux, you should probably use the packages for your distribution which should be +available through your package manager of choice. The minimum SDL version you +need to run this is 1.2.10. On Windows, you can download the SDL and SDL_net dlls +from http://www.libsdl.org/. On Mac, the SDL framework is included in the +application bundle. + +The Mac OS X version includes a simple launcher program. + +Known issues: +- Mouse sensitivity is different than in the original Fitzquake (or vanilla Quake, + for that matter). +- Pasting from the clipboard does not work. +- It is not possible to switch the screen refresh rates from within the engine. +- On Linux, there are problems with the default sound sampling rate of 11025Hz. + This can be fixed by supplying -sndspeed 48000 on the commandline. + +Changes since the March 7 beta: +- discard mouse movement while input is deactivated +- implement maps and mods commands using POSIX functions and added a POSIX wrapper + for Win32 +- activate mouse input when binding a key, otherwise mouse keys cannot be bound + through the menu +- fixed shift key behaviour +- implemented TCP networking +- fixed fog command (use fmax instead of max in Fog_FogCommand_f) +- fixed bug that lead to the screen being set to minimum size when the sizedown + command is issued multiple times +- LSHIFT + ESC and circomflex always opens the console +- print everything to stdout +- and more... + +ChangeLog: +---------- +01/22/2008 +- release mouse pointer when console is active. +- enable key repeats (doesn't work on OS X, test in Win32).c + +01/23/2008 +- implement Sys_SendKeyEvents +- disable mouse input when console is inactive using SDL_SetEventFilter +- move mouse input processing to main event loop +- add vid.mode to determine window mode +- fixed Sys_printf() +- enable key repeats when console is active only +- return 0 if any of the cl_bob vars is 0 in V_CalcBob + +01/24/2008 +- changed in_deactivate so that it does not always release the mouse cursor +- adapted all calls to in_activate and in_deactive, because they need to be called regardless of the current mode +- added platform dependent messagebox code for fatal errors on OS X and Windows + +02/06/2008 +- fixed numlock acting as caps lock issue +- fixed: input is not activated on map command (hopefull got them all this time) +- center window in windowed modes + +2008/03/14 +- discard mouse movement while input is deactivated +- implement maps and mods commands using POSIX functions and added a POSIX wrapper for Win32 +- activate mouse input when binding a key, otherwise mouse keys cannot be bound through the menu +- fixed shift key behaviour + +2008/06/3 +- fixed numerous bugs in sdl_net.c + +2008/06/4 +- fixed fog command (use fmax instead of max in Fog_FogCommand_f) +- fixed bug that lead to the screen being set to minimum size when the sizedown command is issued multiple times + +2008/07/4 +- LSHIFT + ESC and circomflex always opens the console + +2008/07/5 +- print everything to stdout + diff --git a/Misc/fitzquake085.txt b/Misc/fitzquake085.txt new file mode 100644 index 0000000..ad3a468 --- /dev/null +++ b/Misc/fitzquake085.txt @@ -0,0 +1,532 @@ + +================================================================================ + +Fitzquake version 0.85, Feb 12, 2009 + +Filename : fitzquake085.exe +Author : John Fitzgibbons +Email Address : johnfitz@u.washington.edu +Author's Homepage : http://www.celephais.net/ +Fitzquake Homepage : http://www.celephais.net/fitzquake + +Fitzquake is a modified glquake based on the source code released by id +Software. My primary focus is fixing a lot of the rendering bugs which made +glquake inferior to the software renderer. My secondary focus is adding +conveniences for mappers and general users. I am also slowly adding support for +new modding or mapping features such as skyboxes, fog, and colored light. + +While I have made extensive changes to the code, I pretty much use the same +OpenGL features that the original glquake uses. Therefore, if you can run +glquake, you can probably run Fitzquake. + +I am not finished working on this project, so bug reports and feature requests +are welcome. + +Acknowlegements +-------------------------------------------------------------------------------- + +id Software, LordHavoc, Bengt Jardrup, Baker, Aardappel, SmallPileOfGibs, FrikaC, +Vondur, JPL, Negke, preach, Topaz, Tomaz, Tonik, Radix, EvilTypeGuy, NightBringer, +MH, Tyrann, Spirit, Fett, Maddes, Craig Wills, Heffo, Riot, Gleeb, Speedy, +people in #flipcode, & others for their feedback, tutorials, code, testing, +and assistance. + +Copyright / Permissions +-------------------------------------------------------------------------------- + +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place - Suite 330, Boston, MA 02111-1307, USA. + +History +================================================================================ + +changes in 0.85 +--------------- + - increased limits + - added a network protocol 666, which supports higher limits, entity alpha, and more (see command "sv_protocol") + - can load and play all known limits-breaking maps, including masque, warpspasm, indian summer, sickbase, 768_negke, digs04, omlabx, and bod. + - increased MAX_MODELS from 256 to 2048 (and MAX_MOD_KNOWN from 512 to 2048) (requires the new protocol) Prints a warning if you exceed the old limit. + - increased MAX_SOUNDS from 256 to 2048 (requires the new protocol) Prints a warning if you exceed the old limit. + - increased the upper limit for the "max_edicts" cvar to 32000 (requires the new protocol for entities past 8192 to play sounds) + - models with more than 256 frames can now actually use those frames (requires the new protocol) + - fixed crash when marksurfaces > 32767, raising the effective limit to 65k. Prints a warning if you exceed 32767. + - fixed crash or other weird behavior when faces > 32767, raising the effective limit to 65k. Prints a warning if you exceed 32767. + - fixed crash when nodes > 32767, raising the effective limit for leafs+nodes to 65k. Prints a warning if you exceed 32767. + - increased clipnode capacity from 32k to 65k. Prints a warning if you exceed 32767. + - increased MAX_MAP_LEAFS from 8192 to 32k (these are visleafs, not total leafs.) Prints a warning if you exceed the old limit. + - increased MAX_STATIC_ENTITIES from 128 to 512. Prints a warning if you exceed the old limit. + - increased MAX_VISEDICTS from 256 to 1024, which should fix static entities flickering in large maps. Prints a warning if you exceed the old limit. + - increased MAX_EFRAGS from 640 to 2048. Should reduce the "Too many efrags!" messages. Prints a warning if you exceed the old limit. + - increased MAX_LIGHTMAPS from 64 to 256, meaning you can load 4 times as much lightmap data before getting an "Allocblock:full" error. Prints a warning if you exceed the old limit. + - increased MAX_GLTEXTURES from 1024 to 2048 + - increased MAX_BEAMS from 24 to 32 and MAX_TEMP_ENTITIES from 64 to 256, so you can have more lightning bolts now + - increased increase MAX_DLIGHTS from 32 to 64 + - eliminated "packet overflow" errors in single-player mode on all known maps, by increasing MAX_DATAGRAM from 1024 to 32000 (NET_MAXMESSAGE and MAX_MSGLEN were also increased accordingly.) In multiplayer (including COOP,) the MAX_DATAGRAM size is 1400 because the MTU on most internet routers is ~1450, and anything above this could get fragmented and lost since datagram packets use UDP. Prints warning when packetsize exceeds the standard datagram size. + - increased signon buffer capacity, should eliminate sz_getspace errors (example: hard mode coop crash on Travail's qte2m1.bsp.) Prints a warning when signon buffer is too big for standard servers. + - "no free edicts" error now spits you back to the console instead of crashing + - visual improvements + - added model interpolation for animation and movement. See cvars r_lerpmodels, r_lerpmove, r_nolerp_list. + - added interpolation on the gun kick motion, and ability to disable gun kick entirely (see cvar "v_gunkick") + - disabled torches/bolts casting shadows + - more content support + - added support for per-entity alpha, and it works even without a custom progs.dat, works on static entities too, and both bsp and mdl models. Set values directly in the map and/or control it with quakec. When setting alpha, a value of "0" is interpreted as "default", which means 1.0 for most things, but for water polygons it t means obey the value of r_waterwarp. A value greater than 0, up to 1.0, will explicitly set the alpha, and override r_wateralpha for water polygons. A value of -1 will explictly set the alpha to 0 (invisible), and override r_wateralpha. (requires the new protocol, except for alpha -1, which makes things invisible even with protocol 15) + - can playback protocol 15 demos containing the nonstandard U_TRANS bit, as recorded by nehahra, joequake, and aguirre's engine. When the server says it is running protocol 15, and the U_TRANS bit is encountered, fitzquake will assume it's the Nehahra protocol and parse it accordingly. + - added support for -quoth command line switch. Loads quoth content regardless of current gamedir, and enables hipnotic statusbar layout. This allows you to load a mod that requires quoth, but is in a separate directory (i.e. fitzquake.exe -quoth -game warp) + - fullbrights now use additive blending instead of alpha blending. This allows me to support external glowmaps, and entity transparency with fullbrights/glowmaps. In some cases, additive fullbrights are rendered using multitexture, but only if GL_EXT_texture_env_add is available. The command line option -nocombine will disable the use of GL_EXT_texture_env_add. + - added support for external glowmaps (names must end in _luma or _glow) + - added support for external liquid textures (name your files #water instead of *water) + - added support for map-specific texture directories (put textures in /textures/mapname/) + - interface improvements + - improved crosshair accuracy somewhat for protocol 15, and improved it a lot in protocol 666 + - rearranged scorebar so that map titles as long as 40 chars can be displayed without scrolling + - statusbar displayed values now cap at 999 rather than displaying truncated numbers (1001 will be displayed as 999 instead of 001) + - tab completion cycle order now alphabetized as a whole rather than grouped by type + - added scr_conscale cvar (see below) + - added scr_crosshairscale cvar (see below) + - added scr_sbaralpha cvar (see below) + - scr_showfps and scr_clock display now use same scale as console (scr_conscale, scr_conwidth) + - video menu now shows aspect ratio of selected video mode + - added aguirRe's trick of making the window perfectly cover the screen with no titlebar/borders, if you create a windowed mode with height/width equal to the desktop size. (not tested on dual monitors, though.) + - developer features + - added ability to monitor various stats that change dynamically, such as packet size, temp entities, and visedicts. (see cvar "devstats") + - restricted spammy messages "packet overflow", "beam list overflow", and "too many efrags" to repeat at most once every 3 seconds + - r_showbboxes now draws point entities too + - "mcache" command now gives you a total number of models (note, this is a list of all loaded models, not just the current level's model precaches) + - "soundlist" command now gives you a total number of sounds (note, this is a list of all loaded sounds, not just the current level's sound precaches) + - "give" command can now give armor values above 200 + - added imagedump command (see below) + - "x is not a field" message is now shown only if developer = 1 + - bugfixes + - added Baker's fixes for Intel display adapters + - fixed tab completion cycle never reaching certain commands/cvars in list + - fixed -dinput mouse doesn't work after vid_restart + - fixed rotated alias models incorrectly culled + - fixed sky animation on brush models + - fixed "invalid pixel format" error when switching between two video modes with the same bit depth, on certain chipsets (Baker had this problem) + - fixed alt-tab fullscreen trashed window on some nvidia, intel cards (from Baker) + - fixed some TGAs load upside down + - fixed non-square PCX loading appears interlaced + - fixed PCX loading from inside pakfiles appears as random noise + - fixed player skin corruption bug (thanks aguirRe) + - fixed improperly trying to colormap the playermodel before a map is loaded, after switching games (could cause a crash or discolored textures) + - fixed too-dark fog on world/brushmodels with -nomtex + - fixed overbright models too light with fog and -nocombine + - fixed r_wateralpha support on brush entities + - fixed grenade shadows spinning wildly instead of staying on the ground + - fixed solid fogged sky was not quite the same color as solid fogged geometry, for certain colors of fog + - fixed trigger_changelevel with missing map causes apparent hang + - fixed hang when r_stereo and r_lightmap are both enabled + - fixed "W_GetLumpinfo" crash when a graphic is missing from gfx.wad. Prints a console message instead. Missing graphics will be drawn as a checkerboard image. Note: conchars is the one graphic that must be in the gfx.wad, since the console won't even function without it. + - fixed accidentally stripping -hipnotic and -rogue directories when switching games + - fixed give command doesn't update currentammo + - fixed changing gamedirs while a skybox is loaded sometimes corrupts textures/lightmaps in the next map loaded + - fixed unnecessary creation of "glquake" subdirectory + - fixed 0Hz/0bpp showing up in video menu sometimes + - fixed -aliases triggered on alt-tab + - fixed con_logcenterprint considering identical messages to be duplicates even though the map has changed in between + - fixed hang in proxmap2, when going through one of the teleporters (thanks spirit, mh) + - fixed lightning bolt memory corruption crash (rare) + - source cleanup + - removed all ASM code from source, and removed gas2masm project + - cleaned up unnecessary library dependencies in source release + - renamed local variable "errno" to fix a compiling problem on visual c++ express edition + - added dxsdk folder to source release + +cvars: + + - devstats. default 0. If 1, prints various the current and peak values of various stats that change over time, such as packetsize, and edicts. Note: even if devstats is set to 0, fitzquake will keep track of peak values so when you turn it on, the peaks are accurate. Devstats display scale follows console text scale. + + - r_lerpmodels. default 0. If 1, do interpolation on model animations, with exceptions for torches (r_nolerp_list) & muzzleflares (interpolation is suspended for two frames if EF_MUZZLEFLASH is encountered, on the assumption a muzzleflare happens at the same time.) If 2, lerp all animations, without exception. If 0, disable interpolation (to mimic standard quake.) + + - r_lerpmove. default 0. If 1, do movement interpolation on MOVETYPE_STEP entities (i.e. monsters.) If 0, disable interpolation (to mimic standard quake.) I recommend that you use r_lerpmove and r_lerpmodels together, or turn them both off, otherwise monsters will move strangely. + + - r_nolerp_list. Contains a comma-separated list of filenames of models to exclude from lerping. Filename must include the relative path and extension, for example "progs/flame.mdl". The default is "progs/flame.mdl,progs/flame2.mdl,progs/braztall.mdl,progs/brazshrt.mdl,progs/longtrch.mdl,progs/flame_pyre.mdl,progs/v_saw.mdl,progs/v_xfist.mdl,progs/h2stuff/newfire.mdl". This should include all torches in id1, quoth, and bastion, which look bad when lerped, and the zerstorer chainsaw and xmen fist, which also look bad lerped. + + - r_skyfog. default 0.5. Controls how much the sky is obscured by the fog. 0 = sky is completely unfogged, 1 = sky is completely fogged. + + - scr_conscale. default 1. Scales the console text larger, but only has an effect when scr_conwidth is 0. + + - scr_crosshairscale. default 1. Scales the crosshair larger. 1 = normal, 10 = maximum enlargement + + - scr_sbaralpha. default 1. Controls the transparency of the statusbar background. 0 = invisible, 1 = opaque (original quake behavior), in-between values give different levels of transparency. Pro tip: set it to 0.99 and it will still appear opaque, but the 3D view will expand to fill the area on both sides of the statusbar. + + - v_gunkick. default 1. If 1, standard quake view kick when you fire your gun. If 2, interpolates the view kick. If 0, disables view kick entirely. + +commands: + + - imagedump. Dumps all loaded textures from opengl into tga files. This shows the textures as they exist in opengl texture memory. Texture names containing '*' will be renamed with '#' instead. + + - sv_protocol. Sets the network protocol used by the server. Default is 666. Possible values are 15 (standard netquake protocol) or 666 (new fitzquake protocol.) Changes to the protocol will not take effect until the next time you load/reload a level. When running protocol 666, standard clients won't be able to connect to your server, and they won't be able to play any demos you record. When running protocol 15, only 256 models and 256 sounds can be sent to the client, which means you would see invisible entities and not hear some sounds, when running a map or mod that has more than 256 models or sounds. There is also some other data the server will suppress in protocol 15, such as per-entity alpha. Also note, dzip's special demo compression does not work on demos that use modified protocols, so you will get a compression ratio no better than standard zip compression. + +changes in 0.80 +--------------- + + - map loading is about four times faster. This is due to some optimizations in the texture loading code. + - video mode can now be changed in-game. You can change resolution, color depth, refresh rate, and switch between windowed and fullscreen modes. This can be accomplished easily using the video options menu, and is also available at the console using cvars. Note: if you launch fitzquake with -width, -height, -window, or -bpp in the command line, Fitzquake will use the command line settings and ignore whatever it reads from the config files at startup. However, you will still be able to change the video mode from the menu or the console as you like. Also note: to change video modes by execing a config file, the config file must include "vid_restart" after the other vid_ cvars have been set. (see cvars "vid_width," "vid_height," "vid_fullscreen," "vid_bpp," "vid_refreshrate," and commands "vid_restart," "vid_test") + - added control of entity count limits (MAX_EDICTS.) The default maximum has been increased from 600 to 1024. It can be raised even higher (up to 8192 -- this is a limit set by the network protocol) using a cvar. (see cvar "max_edicts") + - added control of vertical sync. (see cvar "vid_vsync") + - added control of anistropic filtering. Anisotropic filtering is a method to improve texture clarity on surfaces when viewed at steep angles. (see cvar "gl_texture_anisotropy") + - console buffer size can now be set. The command line option "-consize" allows you to specify the buffer size in kilobytes. For reference, fitzquake's default buffer size is 64k, and glquake's is 16k. 16k is also the smallest size fitzquake will allow you to set. Note: this buffer lives in the heap, so if you want to have a HUGE buffer, you might need to increase -heapsize also. + - increased max surface extents from 256 (the software quake maximum) to 2000. For reference, the glquake maximum is 512. This should eliminate the "bad surface extents" crash when running some maps that could be played in glquake, but not fitzquake or winquake. + - increased max vertices in an alias model from 1024 (the glquake maximum) to 2000 (the software quake maximum.) + - increased MAX_CHANNELS from 128 to 512 and MAX_DYNAMIC_CHANNELS from 8 to 128 + - screenshot filenames are now in the format "fitz0000.tga", increasing the maximum number of screenshots per folder from one hundred to ten thousand. + - vid_describemodes output cleaned up; now displays a list of valid refresh rates for each mode + - added a more intuitive cvar control of slow motion/fast-forward for demos and live gameplay. (see cvar "host_timescale") + - "reset to defaults" option in the menu now executes the "resetall" command before loading default.cfg + - game command now writes config.cfg to current gamedir before switching + - +mlook is now saved to config.cfg + - changed smallest allowed window size from 320x240 to 320x200 + - console cursor images are now hard-coded, and the insert mode cursor is now a vertical bar instead of an underscore. This was prompted by several popular mods (OUM, Xmen TC) using nonstandard cursors, which are incompatible the fitzquake console enhancements. + - gl_farclip now defaults to 16384. This should be high enough to handle even the largest open areas without unwanted clipping. The only reason you'd want to lower this number is if you see z-fighting. + - added cvar r_drawworld (see below) + - added cvar r_showtris (see below) + - added cvar r_showbboxes (see below) + - added a command to cycle a cvar through a list of values. (see command "cycle") + - mapname command now works on dedicated server. + - added a hack to fix those white edges on the bottom of the large box of shells. I feel dirty, but they look better, now. + - changed stuffcmds behavior to allow hyphenated map names, config file names, etc. in the command line. (example: "fitzquake.exe +exec my-config.cfg +map my-map") However, you still can't load a map or config file when the first character in the filename is a hyphen. + - skybox loader now reverts to scrolling sky if all 6 skybox faces failed to load. + - tweaked circular particles to match the apparent size of the square particles. + - fixed crash when loading mods with large player skins (like the chainsaw mod, and PerQuake.) Colormapping can now handle any size player texture (limited to the heapsize, of course.) + - fixed bug where a replacement model in a mod is messed up becuase a matching .ms2 file in id1/glquake is present. The fix is simply to disable all mesh caching, which slows down model loading a little. + - fixed freeze when gl_overbright is 1 and either texture_env_combine or multitexture is disabled or not supported. + - fixed "numgltextures == MAX_GLTEXTURES" crash when viewing multiplayer->setup menu. + - fixed "numgltextures == MAX_GLTEXTURES" crash when playing large maps (nesp09,) due to frequent model recaches. + - fixed bug where entities and water were not being drawn any frame in which a model had to be recached. + - fixed skybox texture showing up in the place of other textures/skins/lightmaps, when the previous loaded map had a skybox. + - fixed missing polygons on edges of screen when underwater and r_waterwarp is 1. + - fixed missing polygons on edges of screen when r_stereo is enabled. + - fixed long mapnames that scroll are truncated to be shorter than what appears in the console. Well, sort of. What I did was increase the maximum length from 39 to 127. A mapname longer than 127 will still be truncated, but these are pretty rare. + - fixed rogue's teamplay skin showing up as an all-white texture. + - fixed checkerboard texture shows up sometimes when an animated texture has fullbright pixels on some frames, but not others (reactor core in junk.bsp) + - fixed color 255 is not fullbright + - fixed fuscia dots appearing in corners of resampled textures + - fixed mapname command crashes when client is disconnected. + - fixed cl_nolerp 1 screws up speed of demo playback + - fixed annoying client-side step-up smoothing when in noclip mode (except on nonlocal servers.) + - fixed scr_clock 2 displays hour twelve as "0" instead of "12" + - fixed skip textured surfaces still drawn even after running tyrann's skip utility + - fixed crash when loading a map with a sky texture where the "sky" in the texturename is not lowercase + - fixed hang in e2m2 on easy skill, where you can shoot one of the buttons at the beginning and then trick-jump through the open gate. Now it prints a warning message ("SV_TouchLinks: null link") and lets you keep playing. + - fixed bug when viewsize < 100, and r_oldwater is 0, where you can see the water textures overlaying part of the brown frame around the viewport. + - fixed bug where imagelist and r_speeds2 would report the same megabyte counts in both 16bpp and 32bpp mode, even though texture bpp should match (and does match) framebuffer bpp. The numbers are now different and accurate for each bit depth. + - removed cvars "vid_config_x," "vid_config_y," "vid_wait," "vid_nopageflip," "_vid_wait_override," "_vid_default_mode," "_vid_default_mode_win," and "vid_stretch_by_2," none of which glquake ever used. + - removed cvar "vid_mode" and commands "vid_describemode" and "vid_nummodes," because of the new video mode handling. + +cvars: + + - gl_texture_anisotropy. Controls the amount of anisotropy in texture filtering. If 1 or less than 1, texture filtering is normal (isotropic.) If greater than 1, increasing degrees of anisotropic filtering are used, up to the hardware maximum. Set value to 2 for 2x anisotropic, 4 for 4x, etc. Default 1. + + - host_timescale. Scales the passage of time on client and server. Set to 0 or 1 for normal speed, less than 1 for slow motion, and greater than 1 for fast-forward. If greater than 0, overrides host_framerate. Default 0. + + - max_edicts. Sets the maximum number of entites on both the client and server. Default 1024. Acceptable values range from 256 to 8192. Set to 600 to mimic standard quake. Changes won't take effect until the next time a map is loaded. Note: if a client connects to a server, and the client's maximum is lower than the server's, the client will probably crash if it ever sees an entnum higher than its local max_edicts. Warning: you may need to increase -heapsize if you want a high max_edicts value. + + - r_drawworld. If 1, draw world as usual. If 0, don't draw the world. Default 1. (compare r_drawentities) + + - r_showbboxes. If 1, draw a wireframe bounding box around each entity. Note that these are the server-side per-edict physics bounding boxes, not the client-side per-model rendering bboxes. If 0, disable this feature. Default 0. + + - r_showtris. If 1, draw wireframe outlines for every triangle in the scene. Like in Quake 3, the lines will be visible even through solid geometry. If 2, draw the outlines only on visible surfaces (like r_showtris 2 in Medal of Honor.) If 0, disable wireframe overlay. Default 0. + + - vid_bpp. Sets the color depth of the screen in fullscreen mode. Windowed mode ignores this setting. Can be 16 or 32. Default 16. (Changes won't take effect until the next call to vid_restart.) + + - vid_fullscreen. If 1, fitzquake will run fullscreen. If 0, fitzquake will run in a window. Default 1. (Changes won't take effect until the next call to vid_restart.) + + - vid_height. Sets the vertical screen/window resolution. Default 480. In windowed mode, cannot be less than 200. (Changes won't take effect until the next call to vid_restart.) + + - vid_refreshrate. Sets the refresh rate of the screen in fullscreen mode. Windowed mode ignores this setting. Possible values include 60, 70, 72, 75, 85, etc. Default 60. (Changes won't take effect until the next call to vid_restart.) + + - vid_width. Sets the horizontal screen/window resolution. Default 640. In windowed mode, cannot be less than 320. (Changes won't take effect until the next call to vid_restart.) + + - vid_vsync. Set to 1 to enable vertical sync, which eliminates tearing, but caps your framerate at the monitor refreshrate. Set to 0 to disable vertical sync, which allows tearing but doesn't cap your framerate. Default 0. Note: If fitzquake detects that your driver settings have forced vsync to be disabled, it will post a message to the console saying so, and this cvar will have no effect. + +commands: + + - "cycle [ [ ...]]" to cycle a cvar through a list of values.. Note: this command will get stuck on a list that contains the same value more than once, such as "cycle blah 1 2 1 3". If you're doing anything that complex you can just use aliases. + + - vid_restart. Tries to set a video mode that matches the values of vid_width, vid_height, vid_fullscreen, and, if vid_fullscreen is 1, vid_bpp and vid_refreshrate. + + - vid_test. Like vid_restart, except that after switching to the new mode pops up a confirmation dialogue. This is useful if you are not sure what modes your monitor can handle, so you don't get stuck with a blank screen. The dialogue has a time limit so that if you don't press a key within 5 seconds, it will revert to the previous mode. + +changes in 0.75 +--------------- + + - totally rewritten bsp drawing code. The new code combines the advantages of the gl_texsort 1 and gl_texsort 0 codepaths from glquake into one codepath that uses texture sorting and multitexture. In my tests, i've found that it's about the same speed as glquake in low poly scenes (like the original quake levels,) but as you get into the thousands of wpolys, it's faster and faster. + - 2x overbright lighting. Lighting now looks like software quake. Just like overbright lighting on models, overbright on world polys requires GL_EXT_texture_env_combine (TNT and later, voodoo4 and later.) Without it, FitzQuake will be use two passes to render overbright world polys. So if you don't have that extension, you might disable it for performance reasons. (see cvar "gl_overbright") + - colored lighting support using .lit files. + - totally new water surface animation. The new method requires no surface subdivision, isn't plagued by tearing and sparklies caused by tjunctions, and looks almost identical to software quake's water, and doesn't slow down on very large sheets of water. (see cvars "r_waterquality" and "r_oldwater") + - old water aninmation fixed so that it doesn't look bad when gl_subdivide_size is 32 or lower. + - can load external textures (currently targa and pcx) if they match the texture name in the bsp and are located in the id1/textures, or /textures directory. At the moment no other images (skins, menu, etc.) can be replaced. + - gamma correction now goes back to normal when Fitzquake loses focus. + - tab completion now adds a space after the completed command/cvar if the cursor is at the end of the editline. + - increased the max length of the video mode list from 30 to 80. This should alleviate the problems people with newer cards were having trying to use some 32-bit modes -- there were so many 16-bit modes that they filled up the list before all 32-bit modes could be detected. The video menu will still only list a certain number of modes due to space constraints, but you can see the complete list by using the console command "vid_describemodes" + - number of listed video modes in the "video options" menu increased from 27 to 36. + - added alpha control for the front sky layer. (see cvar "r_skyalpha") + - clock can now display time in 24-hour or "military" time. + - added an "mtex" counter to the r_speeds 2 readout. This measures the number of megabytes of texture memory used each frame to draw the scene. Note: this doesn't count textures used to draw the console, menu, or statusbar. + - added optional drawing of surfaces inside sky leaves, for compiler/map testing purposes. (see cvar "r_oldskyleaf") + - sky surfaces on bmodels are now visible, though drawn incorrectly. + - fixed multiple textures in bsp with same name or no name get overwritten/not loaded. (example: rd1m3) + - fixed some nasty texture loading bugs that were especially hounding 3dfx users (wrong texture, no texture, or unnecessarily low-res textures displayed on models and world.) These bugs would also occur in conjunction with nonzero values of gl_max_size or gl_picmip. + - fixed bug where changing gl_max_size or gl_picmip in-game would screw up alias model texture coordinates. + - fixed bug where if -gamma is specified at the command line, the "gamma" cvar would be ignored. + - fixed model more than 2048 units above floor us unlit by static lighting. + - fixed crash with con_logcenterprint when centerprint message was too long. + - fixed crash when changing to a nonexistant gamedir and then trying to write a file (screenshot, etc.) Fitzquake now creates the directory as needed. + - fixed "bad surface extents" error when sky or water surface is missing from bsp file. + - fixed alias model shadows write to z-buffer. + - fixed underwater intermission camera has warp, but no screenblend. + - fixed console buffer still scrolled back after using the "clear" command. + - fixed console command history (the list of previous commands) not being rewound after toggling the console. + - removed cvar gl_texsort. New bsp drawing code always sorts by texture. + - removed cvar gl_ztrick. The depth buffer is now cleared every frame. + - removed cvar gl_keeptjunctions. Extra verts created by qbsp to eliminate tjunctions are now always kept. + - removed "sliding on monsters' heads" fix, becuase I decided I don't like the idea of changing gameplay, even if the original behaviour is clearly a bug. Since this bug can be fixed in quakec also, I feel safer leaving it as it was. + +cvars: + + - gl_overbright. default 1. This variable controls overbright lighting on the world polygons. (For lighting on models, use gl_overbright_models.) If 1, overbright will be enabled and lighting will resemble software Quake. If 0, overbright will be disabled and lighting will resemble GLQuake. + + - r_oldskyleaf. default 0. If 0, surfaces inside sky leaves will be skipped by the renderer. If 1, they will be drawn whenever they are in your PVS, just like any other surface. + + - r_oldwater. default 1. If 1, use the old GLQuake method of subdividing the water surface to enable a warping animation. If 0, use the new render-to-texture method. Note: in general, r_oldwater 0 looks better and runs slower. So experiment to see if the performance hit is acceptable to you. + + - r_skyalpha. default 1. Sets the alpha of the front sky layer. Note that if sky alpha is less than 1.0, the sky will be drawn in two passes even if you have multitexture. + + - r_waterquality. default 8. Sets the quality of the water when r_oldwater is 0. Can be anywhere from 1 to 64. Lower values give better performance, higher values look better. A value of 4 will provide water that looks at least as good as glquake can get, and 32 is close enough to software quake for all but the most picky. To control the quality of water when r_oldwater is 1, use gl_subdivide_size instead. + + - scr_clock. default 0. If 1, game time is displayed. If 2, system time is displayed in 12-hour format. If 3, system time will be displayed in 24-hour or "military" time. + +Changes in 0.70 +--------------- + + - added anaglyph stereo rendering. Note that this will cut your framerate in half, as it is rendering the scene once for each eye. You might want to turn off r_drawviewmodel as it is hard to focus on becuase it is so close. (see cvars "r_stereo", "r_stereodepth") + - now uses ARB_multitexture if present, otherwise tries to use SGIS_multitexture. This should fix various blending bugs on some cards, such as weird cloud layers, all-black models, and inverted torches. (only ARB guarantees that GL_DECAL blending will work) + - overbright models now uses GL_EXT_texture_env_combine if supported (TNT and later, voodoo4 and later,) which saves one or two passes on model rendering when gl_overbright_models is 1. The command line option -nocombine will disable this. + - gl_overbright_models now defaults to 1 + - custom palettes are now correctly loaded when you use the "game" command. + - RecursiveLightPoint is now lerped for smoother lighting of slow-moving models. Check out the ogre in e3m3 for an example. + - dynamic lighting (rockets, etc) has been moderately sped up. (some lightmaps were being uploaded even though they weren't actually touched by a dynamic light.) + - overhauled 2d drawing to allow independent scaling of console, menu, and sbar (see cvars "scr_conwdith," "scr_menuscale," and "scr_sbarscale") command line switches "-conwidth" and "-conheight" removed. cvar "scr_stretch_menus" removed. + - user control of max framerate. (see cvar "host_maxfps") + - improved the accuracy of the FPS counter a bit, but it still seems suspect. + - particles can now be drawn as quads or triangles. (see cvar "r_quadparticles") + - opengl information (vendor, renderer, version, extensions) is no longer printed during initialization. (see command "gl_info") + - rewrote texture management. Now instead of quake's memory usage going up and up forever (becuase textures were uploaded to the opengl.dll and never freed,) all textures will get flushed when you switch games, bringing you back down to where you were when fitzquake first launched. + - gl_texturemode command will now accept a number (1 through 6) as well as the name (gl_linear_mipmap_nearest, etc.) + - gl_describetexturemodes will list all texturemodes. + - the inside of sky leaves are no longer rendered -- so when noclipping inside them, it will look the same as noclipping inside a solid wall. This does not affect gameplay. + - cleaned up intermission display in singleplayer -- no more overlapping numbers + - r_speeds readout modified a bit. (see cvar "r_speeds") + - r_sky_quality now defaults to 12 instead of 8, since skies are now drawn in 1 pass (with multitexture at least) + - gl_texsort now always defaults to 0. (it used to be forced to 1 when multitexture was unavailable) + - fixed bug where models would actually darken when dlights got bright enough + - fixed deathmatch, coop cvars not reset to zero when starting a new game from the single player menu + - fixed r_lightmap doesn't work when gl_texsort = 0. + - fixed inverted lightmaps / no textures / no dynamic light bug when multitexture is disabled and gl_texsort = 0 + - fixed scrolling map title in wrong place when width does not equal conwidth + - fixed a few color borking problems in 16-bit mode. The front sky layer, sprites, and pics with transparency will still look a bit off (as in glquake,) but at least the console image and statusbar background look better. Though it isn't perfect, it should once again look like what you expect from glquake in 16bit. + - fixed pixel gap on both sides of the console in 1024x768 + - fixed wpoly count being slightly lower when gl_texsort = 0 (the count was correct when gl_texsort = 1) + - fixed lmap count always zero when gl_texsort = 1 + - dlight fans are now drawn after water, so that it looks right at least when wateralpha is 1. + - fixed a possible bug with older 3dfx cards (voodoo 1/2/rush) where the gamma cvar might not work (untested) + - fixed old commands showing up in the console prompt after hitting 'enter' + - increased MAX_HANDLES so that you should never see the "out of handles" error message. + - removed cvar "chase_alpha." the transparent player model was buggy and didn't work well in a lot of conditions. + - removed cvar "gl_doubleeys" (yes, that is the correct misspelling) With overbright models, eyes can be seen as easily as in software mode. + - removed cvars "cl_crossx," "cl_crossy," "lcd_x," "lcd_yaw," and "gl_reporttjunctions" which didn't do anything (in glquake, at least). + - removed support for GL_EXT_shared_texture_palette (special 8-bit texture format) + - removed VCR code. command line switches "-record" and "-playback" no longer supported. + - removed support for command line switch "-gamma" -- just use the gamma cvar, or idgamma or something. + +cvars: + + - r_stereo. default 0. If nonzero, the scene will be rendered once in red, and again in cyan, with the two views shifted slightly. If you have 3D glasses you can appreciate this (assumes that the left eye is red and the right eye is cyan.) The value of r_stereo sets the eye separation. If your glasses have red on the right eye, use a negative value to flip the views. + + - r_stereodepth. default 128. Sets the distance at which the two views will converge when stereo rendering is active. + + - scr_conwdith. default 0. Sets the virtual console width, where smaller numbers means larger text. Values larger than window width, or smaller than 320, will be clamped to that range, and all values will be rounded to a multiple of 8. If 0, the window width will be used. Note that values that divide evenly into the window width will make the text look nicest. + + - scr_menuscale. default 1. Sets the scale factor for menus and other centered overlays. If 1, images will be drawn at 1:1 scale. If 2, images will be double size. Menus will never be drawn smaller than 1:1, and never larger than the size of the window. Note that integer values will make the text look nicest. + + - scr_sbarscale. default 1. Sets the scale factor for the statusbar. If 1, images will be drawn at 1:1 scale. If 2, images will be double size. The statusbar will never be drawn smaller than 1:1, and never larger than the width of the window. Note that integer values will make the text look nicest. + + - host_maxfps. default 72. sets the maximum frames per second fitzquake will render (also the maximum number of server frames per second.) Clamped to the range 10 - 1000. Set to 72 to mimic standard quake. + + - r_quadparticles. default 1. If 1, particles are drawn as GL_QUADS instead of GL_TRIANGLES. Quads use 4 verts instead of 3, but the fillrate cost is 1/2 that of triangles. Depending on your card, either one may be faster. This cvar has no effect on the appearance of particles. + + - r_speeds. default 0. Values of 1 and 2 will give you increasing amounts of information. When you see two numbers separated by a slash, the first number is polys, and the second number is passes. + +commands: + + - gl_info. Displays opengl info which was previously displayed during initialization: vendor, renderer, version, and extensions + + - imagelist. Displays a list of loaded textures, and their dimentions. + + - gl_texturemode. Now accepts a number (1 through 6) as well as the name (gl_nearest, etc.) + + - gl_describetexturemodes. Lists all texturemodes. + +Changes in 0.65 +--------------- + + - gamma cvar now supported. (see cvar "gamma") + - fullbrights on models now supported. + - odd-sized world textures are now bilinearly resampled (instead of glquake's nearest pixel resample) + - removed all fixed-size buffers for loading textures; now the only limit is the size of your memory heap (textures will still be downsampled if they are bigger than the hardware can handle) + - styled lights (torch flicker, etc.) can now be disabled (see cvar "r_flatlightstyles") + - sky now uses multitexture if available. + - centerprints are now optionally logged to the console (see cvar "con_logcenterprint") + - number of savegame slots increased to 20 (from 12) + - if a map title is longer than 22 characters, it scrolls marquee-style in the statusbar. + - gl_flashblend defaults to 1 again. (consistency with glquake) + - gl_ztrick defaults to 1 again. (consistency with glquake) + - command line gamma now defaults to 1.0 for all cards. (consistency with glquake, plus hardware gamma is better) + - now checks hardware for maximum texture size. Users of later voodoo cards should be able to see large textures now. (see cvar "gl_max_size") + - now shows AM/PM when scr_clock is 2 + - fixed crash when starting dedicated server + - fixed crash when loading too many textures. (now it throws a sys_error and quits) + - fixed fitzquake-specific crash when player goes near water in a demo playback. + - scr_conalpha now actually works. + - keypad enter in the console works again. + - optional 2x overbrightening on models. (see cvar "gl_overbright_models") + - optional quake2-style noclip. (see cvar "sv_altnoclip") + - new icon + +cvars: + + - con_logcenterprint: If 1, centerprint messages will be logged to the console in sp/coop. If 2, they will also be logged in deathmatch. Default 1. + + - gamma: Brighten or darken the screen to compensate for differences between monitors. This is now supported by using your video card's gamma support. Just like in winquake, values less than 1 are brighter and values greater than one are darker. Default 1. Notes: I have added special code so that this will work on 3dfx cards too, but i have no way to check that it works. If fitzquake crashes, your hardware gamma may be stuck at the wrong value. You can use the "display settings" control panel fix this, or you could try lordhavoc's useful "setgamma" utility (available on this page.) Also note that if texture-brightening gamma has been requested at the command line (fitzquake.exe -gamma ), harware gamma will not be used and the gamma cvar will have no effect. + + - gl_max_size: Now defaults to 0. If 0, textures will be as large as the hardware allows. Positive values can be used to impose a limit smaller than the hardware's reported maximum. + + - gl_overbright_models: If 1, models will be rendered using 2x overbrightening and will appear at roughly the same brightness as they do in software quake, which is noticably brighter than the default fitzquake / glquake appearance. I disabled this cvar by default becuase this is still a poorly supported feature -- it currently takes 2 or 3 passes to render a model when this feature is enabled, compared to only 1 pass when it is disabled. Default 0. + + - r_flatlightstyles: If 1, styled lights (torch flicker, etc.) will be displayed as a steady light. If 2, the peak intensity will be used instead of the average intensity. Default 0. + + - sv_altnoclip: If 1, enable the alternative noclip movement which resembles quake2 and is not constrained to the horizontal plane. Set to 0 to retain quake's original noclip behavior. Default 1. + +Changes in 0.60 +--------------- + + - graphics: + - better sky projection, configurable for performance + - menus and other overlays are now centered on the screen, and can optionally stretch to fit resolution. + - all 2d graphics now obey gl_texturemode and all texturemode changes take effect immediately + - underwater warping now resembles quake3's perspective munging, and obeys r_waterwarp + - fixed frustum culling b0rked (giving HOM at certain FOV / vidsize / screensize combinations) + - fixed lack of support for VP_PARALLEL_ORIENTED, VP_PARALLEL_UPRIGHT, and FACING_UPRIGHT sprites + - fixed fullbrights not displayed on world/bmodels + - fixed texture cache mismatch error + - fixed pink edges on sprites, menu items, etc + - fixed particle z-buffer bug (apparent when a particle is in front of a water surface) + - fixed ugly resampling of non-power of two gfx, skins, sprites + - fixed large models (shamblers, shub) dissapearing near edge of screen + - fixed statusbar not visible when gl_clear = 1 + - fixed r_fullbright change not immedate when gl_texsort = 0 + - fixed alias models still dark when r_fullbright = 1 + - fixed HOM when screen is partially underwater + - fixed gunshot decals not showing up on some walls + - sky and water warp now freeze when you pause, as well as lightning bolts + - r_novis changes now take effect immediately, rather than when you cross a leaf boundary + - removed mirror code + - two particle styles, circle and square (cvar controlled) + - console: + - improved tab completion: + - hitting tab once will display a list of possible matches and complete the line using the first match + - hitting tab or shift-tab will cycle through the matches + - autocomplete will now match against aliases as well as commands and cvars. + - autocomplete will now complete later in the string (e.g. "bind mouse1 +att" + tab will complete '+attack') + - autocomplete will even complete inside the string (e.g. "bind m +attack" + tab will complete 'mouse1' if the cursor is right after the 'm') + - new key functionality: + - tab -- autocomplete, cycle through multiple matches + - shift-tab -- cycle backwards through multiple matches + - ins -- toggle insertmode + - del -- delete current character + - backspace -- delete previous character + - ctrl-v -- paste from windows clippboard + - leftarrow -- move cursor one character left in line + - rightarrow -- move cursor one character right in line. or, get one character from the previous command. + - home -- move cursor to beginning of line + - end -- move cursor to end of line + - ctrl-pgup -- scroll up a screen at a time + - ctrl-pgdn -- scroll down a screen at a time + - ctrl-home -- scroll to top of console history + - ctrl-end -- scroll to bottom of console history + - carets indicate that you are scrolled back, like in quakeworld/quake2 + - left arrow, right arrow, pgup, pgdn keys now auto-repeat + - quadrupled the length of the console history + - commands: + - "game " to load a mod. + - "reset " to reset a cvar to default. Note that this is the engine default, not the default.cfg value + - resetall. resets all cvars. + - mods. lists all child folders of quake directory which contain either a progs.dat or a pak file + - maps. lists all addon levels available (i.e. all levels that are not in id1/*.pak) + - mapname. outputs the short name of the current level (e.g. "e1m5") + - cmdlist. alphabetized. 'cmdlist blah' will list only cmds that start with 'blah' + - cvarlist. alphabetized. 'cvarlist blah' will list only cvars that start with 'blah' + - "inc [amount]" to increment a cvar by amount. amount defaults to 1. + - "toggle " to invert the value of a cvar (nonzero -> 0 and 0 -> 1) + - god, noclip, notarget, and fly can now be explicitly set. example: "god 0" will disable god mode + - viewpos. outputs (X,Y,Z) pitch yaw roll + - "give a " now gives you armour. type depends on value + - bindlist. lists all key bindings + - "alias " now outputs the current command string + - "unalias " to delete an alias + - unaliasall. delete all aliases. + - condump. dumps console to condump.txt + - fog. set global fog. syntax is "fog ", "fog ", or "fog " See section on modding for details. Set density to 0 to disable fog. + - "sky " to load a skybox. if skyname is "", this will turn off skybox rendering. + - stuffcmds now parses the "cmdline" cvar rather than the args actually passed to the program. This is useful for loading mods dynamically, so you can edit the cmdline before execing quake.rc (which calls "stuffcmds") + - cvars: + - scr_stretch_menus. default 1. if 1, menus and other overlays are stretched to fill the screen + - scr_conalpha. default 1. This is the opacity when the console is halfscreen. 0.6 will mimic glquake + - scr_clock. default 0. if 1, game time is displayed. If 2, system time is displayed. + - scr_showfps. default 0. if 1, FPS are displayed. + - r_waterwarp recognized. default 1. if zero, no underwater warping will take place + - r_drawflat recognized. default 0. if 1, polygons will be drawn as a solid color with no lightmap or texture + - r_particles. default 1. 0 = no particles, 1 = circular particles, 2 = square particles + - r_fastsky. default 0. if 1, sky will be rendered as solid color, for added performance + - r_sky_quality. default 8. Higher number divides the sky more, for a smoother effect and slower performance. + - r_clearcolor now supported. default 2. specify a palette index from 0 to 255. + - gl_fullbrights. default 1. set to 0 to disable fullbrights + - gl_farclip. default 8192. set to 4096 to mimic glquake. note that the skybox will be drawn somewhat closer than this value. + - cl_maxpitch. default 90 (straight down.) set to 80 to mimic normal quake + - cl_minpitch. default -90 (straight up.) set to -70 to mimic normal quake + - cl_keypad. default 1. if 0, keypad keys will respond as in quake.exe (for example, pushing 'KP_END' will be the same as pushing '1') + - gl_flashblend now defaults to 0. + - gl_ztrick now defaults to 0. + - gl_keeptjunctions now defaults to 1. (note, contrary to the name of this cvar, what is being kept is the extra polygon verts which *eliminate* tjunctions. This is a good thing, so i made it default to 1) + - chase_alpha. default 1. lower values make the chasecam player model translucent. Buggy. + - keyboard + - keypad keys are now bindable: + KP_NUMLOCK, KP_SLASH, KP_STAR, KP_MINUS, KP_HOME, KP_UPARROW, KP_PGUP, KP_PLUS, KP_LEFTARROW, KP_5, KP_RIGHTARROW, KP_END, KP_DOWNARROW, KP_PGDN, KP_ENTER, KP_INS, KP_DEL + - command line: + - running with -gamma at the command line will set the gamma. There is still no way to change this value in game. Default is 1.0 for 3dfx cards, 0.7 for all others + - if unspecified, -conwidth now defaults to -width + - modding extensions: + - skyboxes (worldspawn and qc settable), currently only targa and pcx formats accepted + - global fog (worldspawn and qc settable) + - physics + - fixed sliding around while standing on solid entities' bounding boxes (monsters, players, etc) + - misc + - default heapsize is now 32mb (was 16mb) + - default zonesize is now 256k (was 48k) + diff --git a/Misc/fix_externaltex.patch b/Misc/fix_externaltex.patch new file mode 100644 index 0000000..d18ac0f --- /dev/null +++ b/Misc/fix_externaltex.patch @@ -0,0 +1,113 @@ +-- from quake retexturing project + +diff --git a/Quake/gl_model.c b/Quake/gl_model.c +index 239d361..80948f1 100644 +--- a/Quake/gl_model.c ++++ b/Quake/gl_model.c +@@ -39,6 +39,8 @@ + static cvar_t external_ents = {"external_ents", "1", CVAR_ARCHIVE}; + static cvar_t external_vis = {"external_vis", "1", CVAR_ARCHIVE}; + ++extern cvar_t r_externaltexture_fix; //mk ++ + static byte *mod_novis; + static int mod_novis_capacity; + +@@ -549,6 +551,13 @@ static void Mod_LoadTextures (lump_t *l) + { + if (!q_strncasecmp(tx->name,"sky",3)) //sky texture //also note -- was Q_strncmp, changed to match qbsp + { ++ if (r_externaltexture_fix.value) { //mk ++ if (strstr(tx->name,"sky4")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==13039) ++ q_strlcpy(tx->name, "sky1", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ } + if (loadmodel->bspversion == BSPVERSION_QUAKE64) + Sky_LoadTextureQ64 (loadmodel, tx); + else +@@ -606,6 +615,50 @@ static void Mod_LoadTextures (lump_t *l) + //external textures -- first look in "textures/mapname/" then look in "textures/" + mark = Hunk_LowMark (); + COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); ++ ++ if (r_externaltexture_fix.value) { //mk ++ if (strstr(tx->name,"plat_top1")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==24428) ++ q_strlcpy(tx->name, "plat_top1_cable", sizeof(tx->name)); ++ else ++ q_strlcpy(tx->name, "plat_top1_bolt", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ if (strstr(tx->name,"metal5_2")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==49173) ++ q_strlcpy(tx->name, "metal5_2_x", sizeof(tx->name)); ++ else ++ q_strlcpy(tx->name, "metal5_2_arc", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ if (strstr(tx->name,"metal5_4")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==20977) ++ q_strlcpy(tx->name, "metal5_4_double", sizeof(tx->name)); ++ else ++ q_strlcpy(tx->name, "metal5_4_arc", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ if (strstr(tx->name,"metal5_8")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==48444) ++ q_strlcpy(tx->name, "metal5_8_rune", sizeof(tx->name)); ++ else ++ q_strlcpy(tx->name, "metal5_8_back", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ if (strstr(tx->name,"metal5_8")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==48444) ++ q_strlcpy(tx->name, "metal5_8_rune", sizeof(tx->name)); ++ else ++ q_strlcpy(tx->name, "metal5_8_back", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ if (strstr(tx->name,"window03")) { ++ if (CRC_Block((byte *)(tx+1), tx->width * tx->height)==63697) // e4m2 variant ++ q_strlcpy(tx->name, "window03_e4m2", sizeof(tx->name)); ++ Con_Printf(" using %s\n", tx->name); ++ } ++ } ++ + q_snprintf (filename, sizeof(filename), "textures/%s/%s", mapname, tx->name); + data = Image_LoadImage (filename, &fwidth, &fheight); + if (!data) +diff --git a/Quake/gl_rmain.c b/Quake/gl_rmain.c +index 6883161..4a8a745 100644 +--- a/Quake/gl_rmain.c ++++ b/Quake/gl_rmain.c +@@ -100,6 +100,8 @@ + extern cvar_t r_vfog; + //johnfitz + ++cvar_t r_externaltexture_fix = {"r_externaltexture_fix", "0", CVAR_ARCHIVE}; //mk ++ + cvar_t gl_zfix = {"gl_zfix", "0", CVAR_NONE}; // QuakeSpasm z-fighting fix + + cvar_t r_lavaalpha = {"r_lavaalpha","0",CVAR_NONE}; +diff --git a/Quake/gl_rmisc.c b/Quake/gl_rmisc.c +index 963d55e..e9b251b 100644 +--- a/Quake/gl_rmisc.c ++++ b/Quake/gl_rmisc.c +@@ -47,6 +47,7 @@ + extern cvar_t r_noshadow_list; + //johnfitz + extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix ++extern cvar_t r_externaltexture_fix; //mk + + extern gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz + +@@ -230,6 +231,7 @@ void R_Init (void) + Cvar_SetCallback (&r_lavaalpha, R_SetLavaalpha_f); + Cvar_SetCallback (&r_telealpha, R_SetTelealpha_f); + Cvar_SetCallback (&r_slimealpha, R_SetSlimealpha_f); ++ Cvar_RegisterVariable (&r_externaltexture_fix);//mk + + R_InitParticles (); + R_SetClearColor_f (&r_clearcolor); //johnfitz diff --git a/Misc/fs_search_order.patch b/Misc/fs_search_order.patch new file mode 100644 index 0000000..7818079 --- /dev/null +++ b/Misc/fs_search_order.patch @@ -0,0 +1,34 @@ +allow plain files to override files inside a PAK -- Sander van Dijk. + +diff --git a/Quake/common.c b/Quake/common.c +index c80450f..6074711 100644 +--- a/Quake/common.c ++++ b/Quake/common.c +@@ -2107,13 +2107,6 @@ static void COM_AddGameDirectory (const char *base, const char *dir) + else path_id = 1U; + + _add_path: +- // add the directory to the search path +- search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); +- search->path_id = path_id; +- q_strlcpy (search->filename, com_gamedir, sizeof(search->filename)); +- search->next = com_searchpaths; +- com_searchpaths = search; +- + // add any pak files in the format pak0.pak pak1.pak, ... + for (i = 0; ; i++) + { +@@ -2145,6 +2138,13 @@ _add_path: + if (!pak) break; + } + ++ // add the directory to the search path -- moved here from before the pakX.pak loop -- svdijk. ++ search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); ++ search->path_id = path_id; ++ q_strlcpy (search->filename, com_gamedir, sizeof(search->filename)); ++ search->next = com_searchpaths; ++ com_searchpaths = search; ++ + if (!been_here && host_parms->userdir != host_parms->basedir) + { + been_here = true; diff --git a/Misc/mk_header.c b/Misc/mk_header.c new file mode 100644 index 0000000..de155dc --- /dev/null +++ b/Misc/mk_header.c @@ -0,0 +1,102 @@ +/* +gcc -Wall mk_header.c -o mk_header + +dumps the bytes of given input to a C header as +comma separated hexadecimal values. the output +header can be used in a C source like: + +const char bin[] = +{ +# include "output.h" +}; + +*/ + +#include +#include +#include + +int main (int argc, char **argv) +{ + FILE *f; + struct stat s; + unsigned char *buf, *ptr; + const char *output; + long i, j; + + if (argc != 2 && argc != 3) + { + printf ("Usage: mk_header [output]\n" + "Default output file is \"output.h\"\n"); + return 1; + } + + if (stat(argv[1], &s) == -1 || + ! S_ISREG(s.st_mode) ) + { + printf ("Couldn't stat %s\n", argv[1]); + return 1; + } + + if (s.st_size == 0) + { + printf ("%s is an empty file\n", argv[1]); + return 1; + } + + buf = (unsigned char *) malloc (s.st_size); + if (buf == NULL) + { + printf ("Couldn't malloc %ld bytes\n", + (long)s.st_size); + return 1; + } + + f = fopen (argv[1], "rb"); + if (f == NULL) + { + free(buf); + printf ("Couldn't open %s\n", argv[1]); + return 1; + } + + if (fread (buf, 1, s.st_size, f) != (size_t) s.st_size) + { + fclose (f); + free (buf); + printf ("Error reading %s\n", argv[1]); + return 1; + } + fclose (f); + + output = (argc == 3) ? argv[2] : "output.h"; + f = fopen (output, "wb"); + if (!f) + { + free (buf); + printf ("Couldn't open %s\n", output); + return 1; + } + + for (i = 0, j = 0, ptr = buf; i < s.st_size; ++i) + { + fprintf (f, "0x%02x", *ptr++); + if (i == s.st_size - 1) + break; + fprintf (f, ","); + if (++j < 16) + fprintf (f, " "); + else + { + j = 0; + fprintf (f, "\n"); + } + } + fprintf (f, "\n"); + + fclose (f); + free (buf); + + return 0; +} + diff --git a/Misc/qs_pak/Makefile b/Misc/qs_pak/Makefile new file mode 100644 index 0000000..ea51a26 --- /dev/null +++ b/Misc/qs_pak/Makefile @@ -0,0 +1,18 @@ +INPUT := gfx/conback.lmp \ + maps/e1m1@c49d.ent \ + maps/e1m2@0caa.ent \ + maps/e1m4@958e.ent \ + maps/e2m2@fbfe.ent \ + maps/e2m3@237a.ent \ + maps/e2m7@10a8.ent \ + default.cfg + +OUTPUT := quakespasm.pak + +$(OUTPUT): $(INPUT) + ./mkpak.sh $(INPUT) > $(OUTPUT) + +.PHONY: clean +clean: + rm -f $(OUTPUT) + diff --git a/Misc/qs_pak/default.cfg b/Misc/qs_pak/default.cfg new file mode 100644 index 0000000..25a4b05 --- /dev/null +++ b/Misc/qs_pak/default.cfg @@ -0,0 +1,118 @@ +// +// load keybindings +// +// commands with a leading + will also be called for key up events with +// the + changed to a - +unbindall + +// +// character controls +// + +bind ALT +strafe + +bind , +moveleft +bind a +moveleft +bind . +moveright +bind d +moveright +bind DEL +lookdown +bind PGDN +lookup +bind END centerview + +bind e +moveup +bind c +movedown +bind SHIFT +speed +bind CTRL +attack +bind UPARROW +forward +bind w +forward +bind DOWNARROW +back +bind s +back +bind LEFTARROW +left +bind RIGHTARROW +right + +bind SPACE +jump +//bind ENTER +jump + +bind TAB +showscores + +bind 1 "impulse 1" +bind 2 "impulse 2" +bind 3 "impulse 3" +bind 4 "impulse 4" +bind 5 "impulse 5" +bind 6 "impulse 6" +bind 7 "impulse 7" +bind 8 "impulse 8" + +bind 0 "impulse 0" + +bind / "impulse 10" // change weapon +bind MWHEELDOWN "impulse 10" +bind MWHEELUP "impulse 12" + +// zoom +alias zoom_in "sensitivity 2;fov 90;wait;fov 70;wait;fov 50;wait;fov 30;wait;fov 10;wait;fov 5;bind F11 zoom_out" +alias zoom_out "sensitivity 4;fov 5;wait;fov 10;wait;fov 30;wait;fov 50;wait;fov 70;wait;fov 90;bind F11 zoom_in; sensitivity 3" +bind F11 zoom_in + +// Function keys +bind F1 "help" +bind F2 "menu_save" +bind F3 "menu_load" +bind F4 "menu_options" +bind F5 "menu_multiplayer" +bind F6 "echo Quicksaving...; wait; save quick" +bind F9 "echo Quickloading...; wait; load quick" +bind F10 "quit" +bind F12 "screenshot" + +// mouse options +bind \ +mlook + +// +// client environment commands +// +bind PAUSE "pause" +bind ESCAPE "togglemenu" +bind ~ "toggleconsole" +bind ` "toggleconsole" + +bind t "messagemode" + +bind + "sizeup" +bind = "sizeup" +bind - "sizedown" + +bind INS +klook + +// +// mouse buttons +// +bind MOUSE1 +attack +//bind MOUSE2 +forward +bind MOUSE2 +jump +//bind MOUSE3 +mlook + +// +// game controller +// +bind LSHOULDER "impulse 12" +bind RSHOULDER "impulse 10" +bind LTRIGGER +jump +bind RTRIGGER +attack + +// +// default cvars +// +gamma 1.0 +volume 0.7 +sensitivity 3 + +//viewsize 100 +viewsize 110 +scr_conscale 1.6 +scr_menuscale 1.6 +scr_sbarscale 1.6 + +// default to mouse-look enabled ++mlook diff --git a/Misc/qs_pak/default.cfg.diff b/Misc/qs_pak/default.cfg.diff new file mode 100644 index 0000000..085513f --- /dev/null +++ b/Misc/qs_pak/default.cfg.diff @@ -0,0 +1,76 @@ +--- default.cfg~ ++++ default.cfg +@@ -12,24 +12,26 @@ + bind ALT +strafe + + bind , +moveleft ++bind a +moveleft + bind . +moveright ++bind d +moveright + bind DEL +lookdown + bind PGDN +lookup + bind END centerview +-bind z +lookdown +-bind a +lookup + +-bind d +moveup ++bind e +moveup + bind c +movedown + bind SHIFT +speed + bind CTRL +attack + bind UPARROW +forward ++bind w +forward + bind DOWNARROW +back ++bind s +back + bind LEFTARROW +left + bind RIGHTARROW +right + + bind SPACE +jump +-bind ENTER +jump ++//bind ENTER +jump + + bind TAB +showscores + +@@ -45,6 +47,8 @@ + bind 0 "impulse 0" + + bind / "impulse 10" // change weapon ++bind MWHEELDOWN "impulse 10" ++bind MWHEELUP "impulse 12" + + // zoom + alias zoom_in "sensitivity 2;fov 90;wait;fov 70;wait;fov 50;wait;fov 30;wait;fov 10;wait;fov 5;bind F11 zoom_out" +@@ -85,14 +89,30 @@ + // mouse buttons + // + bind MOUSE1 +attack +-bind MOUSE2 +forward +-bind MOUSE3 +mlook ++//bind MOUSE2 +forward ++bind MOUSE2 +jump ++//bind MOUSE3 +mlook ++ ++// ++// game controller ++// ++bind LSHOULDER "impulse 12" ++bind RSHOULDER "impulse 10" ++bind LTRIGGER +jump ++bind RTRIGGER +attack + + // + // default cvars + // +-viewsize 100 + gamma 1.0 + volume 0.7 + sensitivity 3 + ++//viewsize 100 ++viewsize 110 ++scr_conscale 1.6 ++scr_menuscale 1.6 ++scr_sbarscale 1.6 ++ ++// default to mouse-look enabled +++mlook diff --git a/Misc/qs_pak/gfx/conback.lmp b/Misc/qs_pak/gfx/conback.lmp new file mode 100644 index 0000000000000000000000000000000000000000..09d68e6f3accbb56bef4450e07ba905597042848 GIT binary patch literal 327688 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 lW>6<G^IT1gG(qZ|0tTWneod_jeExUfcjfO7 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

Wp8^m-UI5Q`)Zh%hE*Z9do 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 literal 0 HcmV?d00001 diff --git a/Misc/qs_pak/maps/e1m1.diff b/Misc/qs_pak/maps/e1m1.diff new file mode 100644 index 0000000..f834b9c --- /dev/null +++ b/Misc/qs_pak/maps/e1m1.diff @@ -0,0 +1,18 @@ +--- e1m1.ent ++++ e1m1@c49d.ent +@@ -715,14 +715,15 @@ + { + "classname" "func_door" + "targetname" "t4" + "angle" "-2" + "spawnflags" "1" + "sounds" "2" + "model" "*15" ++"lip" "7" // svdijk -- added to prevent z-fighting + } + { + "classname" "trigger_multiple" + "target" "t4" + "health" "1" + "model" "*16" + } diff --git a/Misc/qs_pak/maps/e1m1@c49d.ent b/Misc/qs_pak/maps/e1m1@c49d.ent new file mode 100644 index 0000000..3b22d6f --- /dev/null +++ b/Misc/qs_pak/maps/e1m1@c49d.ent @@ -0,0 +1,1970 @@ +{ +"worldtype" "2" +"sounds" "6" +"classname" "worldspawn" +"wad" "gfx/base.wad" +"message" "the Slipgate Complex" +} +{ +"classname" "info_player_start" +"origin" "480 -352 88" +"angle" "90" +} +{ +"classname" "light" +"origin" "480 96 168" +"light" "250" +} +{ +"classname" "light" +"origin" "480 288 168" +"light" "250" +} +{ +"classname" "light" +"origin" "272 96 80" +} +{ +"origin" "272 288 80" +"classname" "light" +} +{ +"classname" "light" +"origin" "272 192 80" +} +{ +"origin" "688 192 80" +"classname" "light_fluorospark" +"style" "10" +} +{ +"style" "10" +"classname" "light" +"origin" "688 288 80" +} +{ +"origin" "688 96 80" +"classname" "light" +"style" "10" +} +{ +"classname" "light" +"origin" "480 -280 168" +"light" "200" +} +{ +"origin" "480 -144 168" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "480 -376 120" +"light" "200" +} +{ +"light" "160" +"origin" "480 -40 168" +"classname" "light" +} +{ +"speed" "400" +"sounds" "2" +"angle" "270" +"classname" "func_door" +"model" "*1" +} +{ +"speed" "400" +"angle" "90" +"classname" "func_door" +"model" "*2" +} +{ +"light" "250" +"origin" "592 544 88" +"classname" "light_fluoro" +} +{ +"origin" "456 600 104" +"classname" "light" +} +{ +"light" "180" +"origin" "688 648 136" +"classname" "light" +} +{ +"classname" "light" +"origin" "688 520 136" +"light" "180" +} +{ +"origin" "688 480 80" +"classname" "item_armor1" +} +{ +"angle" "180" +"spawnflags" "768" +"origin" "616 72 40" +"classname" "monster_army" +} +{ +"light" "250" +"origin" "0 576 120" +"classname" "light" +} +{ +"light" "180" +"origin" "160 576 72" +"classname" "light" +} +{ +"light" "200" +"origin" "560 -32 72" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "400 -32 72" +} +{ +"light" "200" +"origin" "0 712 72" +"classname" "light" +} +{ +"classname" "light" +"origin" "0 728 -136" +"light" "200" +} +{ +"light" "200" +"origin" "0 592 -136" +"classname" "light" +} +{ +"wait" "5" +"angle" "-2" +"sounds" "2" +"targetname" "t1" +"classname" "func_door" +"dmg" "10" +"model" "*3" +} +{ +"sounds" "1" +"target" "t1" +"angle" "180" +"classname" "func_button" +"model" "*4" +} +{ +"light" "200" +"origin" "412 780 136" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "328 904 72" +} +{ +"light" "200" +"origin" "168 800 72" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "-72 864 72" +} +{ +"origin" "264 888 -136" +"classname" "light" +} +{ +"classname" "light" +"origin" "-8 992 -136" +"light" "200" +} +{ +"light" "250" +"classname" "light" +"origin" "272 1064 -136" +} +{ +"light" "250" +"origin" "-8 1232 -136" +"classname" "light" +} +{ +"light" "250" +"classname" "light" +"origin" "256 1272 -136" +} +{ +"light" "250" +"origin" "312 1464 -136" +"classname" "light" +} +{ +"light" "200" +"origin" "128 968 72" +"classname" "light" +} +{ +"light" "250" +"classname" "light" +"origin" "-48 1168 72" +} +{ +"light" "250" +"origin" "312 1168 72" +"classname" "light" +} +{ +"light" "220" +"classname" "light" +"origin" "128 1504 -120" +} +{ +"light" "250" +"classname" "light" +"origin" "-56 1464 -136" +} +{ +"sounds" "2" +"classname" "func_door" +"angle" "180" +"speed" "400" +"model" "*5" +} +{ +"classname" "func_door" +"angle" "0" +"speed" "400" +"model" "*6" +} +{ +"classname" "light_fluoro" +"origin" "176 1744 -152" +} +{ +"origin" "80 1744 -152" +"classname" "light_fluoro" +} +{ +"light" "250" +"origin" "-232 1600 -136" +"classname" "light" +} +{ +"light" "250" +"classname" "light" +"origin" "488 1600 -136" +} +{ +"origin" "-56 1448 72" +"classname" "light" +"light" "250" +} +{ +"light" "250" +"classname" "light" +"origin" "312 1448 72" +} +{ +"light" "260" +"classname" "light_fluoro" +"origin" "416 2064 -112" +} +{ +"light" "260" +"origin" "416 1968 -112" +"classname" "light_fluoro" +} +{ +"light" "250" +"origin" "128 1880 -112" +"classname" "light" +} +{ +"origin" "616 1944 -88" +"classname" "light" +} +{ +"style" "10" +"classname" "light_fluorospark" +"origin" "344 2216 -88" +} +{ +"light" "180" +"origin" "352 2016 -112" +"classname" "light" +} +{ +"classname" "light" +"origin" "128 2056 -112" +"light" "250" +} +{ +"light" "250" +"origin" "-112 1984 -112" +"classname" "light" +} +{ +"light" "350" +"origin" "-472 2064 -88" +"classname" "light_fluoro" +} +{ +"classname" "light" +"origin" "-192 2208 8" +"light" "250" +} +{ +"light" "250" +"origin" "-424 2208 8" +"classname" "light" +} +{ +"light" "250" +"origin" "-248 2088 -96" +"classname" "light" +} +{ +"origin" "-200 2384 -72" +"classname" "light" +} +{ +"classname" "light" +"origin" "-424 2384 -72" +} +{ +"light" "200" +"origin" "-448 2408 -128" +"classname" "light" +} +{ +"classname" "light" +"origin" "-176 2408 -128" +"light" "200" +} +{ +"sounds" "1" +"classname" "func_plat" +"model" "*7" +} +{ +"light" "350" +"origin" "-352 2656 184" +"classname" "light" +} +{ +"light" "350" +"classname" "light" +"origin" "-352 2464 184" +} +{ +"origin" "-576 2800 -40" +"classname" "light" +} +{ +"light" "500" +"origin" "160 2920 232" +"classname" "light" +} +{ +"classname" "light" +"origin" "160 2720 232" +"light" "500" +} +{ +"origin" "-288 2992 8" +"classname" "light" +} +{ +"classname" "light" +"origin" "-168 2776 -40" +} +{ +"classname" "light" +"origin" "160 2824 104" +"light" "200" +} +{ +"light" "150" +"origin" "-64 2760 136" +"classname" "light" +} +{ +"light" "200" +"origin" "16 2832 -152" +"classname" "light" +} +{ +"classname" "light" +"origin" "304 2832 -152" +"light" "200" +} +{ +"origin" "504 2816 16" +"classname" "light" +} +{ +"sounds" "3" +"wait" "-1" +"speed" "600" +"targetname" "t2" +"spawnflags" "1" +"angle" "270" +"classname" "func_door" +"model" "*8" +} +{ +"classname" "light" +"origin" "160 2840 -152" +"light" "200" +} +{ +"light" "80" +"origin" "16 2904 -88" +"classname" "light" +} +{ +"classname" "light" +"origin" "304 2904 -88" +"light" "80" +} +{ +"classname" "light" +"origin" "160 2904 -88" +"light" "80" +} +{ +"wait" "-1" +"sounds" "1" +"target" "t2" +"speed" "50" +"angle" "270" +"classname" "func_button" +"model" "*9" +} +{ +"light" "100" +"origin" "0 1800 -32" +"classname" "light" +} +{ +"classname" "light" +"origin" "248 1800 -32" +"light" "100" +} +{ +"style" "32" +"targetname" "t3" +"origin" "8 2352 200" +"classname" "light" +} +{ +"style" "32" +"targetname" "t3" +"classname" "light" +"origin" "32 2392 200" +} +{ +"style" "32" +"targetname" "t3" +"origin" "56 2352 200" +"classname" "light" +} +{ +"style" "32" +"targetname" "t3" +"classname" "light" +"origin" "32 2312 200" +} +{ +"style" "32" +"targetname" "t3" +"light" "200" +"origin" "32 2352 88" +"classname" "light" +} +{ +"spawnflags" "2048" +"origin" "112 2352 16" +"classname" "weapon_nailgun" +} +{ +"sounds" "3" +"targetname" "t3" +"spawnflags" "3" +"angle" "270" +"classname" "func_door_secret" +"model" "*10" +} +{ +"style" "32" +"sounds" "3" +"target" "t3" +"classname" "trigger_once" +"model" "*11" +} +{ +"origin" "304 2368 96" +"classname" "light" +} +{ +"angle" "180" +"origin" "248 2392 40" +"classname" "monster_army" +} +{ +"origin" "272 2352 64" +"classname" "item_spikes" +} +{ +"style" "32" +"sounds" "3" +"target" "t3" +"classname" "trigger_once" +"model" "*12" +} +{ +"origin" "832 2608 16" +"classname" "light" +"light" "220" +} +{ +"light" "220" +"classname" "light" +"origin" "832 2480 0" +} +{ +"light" "240" +"origin" "800 2816 24" +"classname" "light" +} +{ +"style" "33" +"targetname" "t11" +"spawnflags" "1" +"classname" "light" +"origin" "752 2000 -88" +"light" "400" +} +{ +"style" "34" +"spawnflags" "1" +"targetname" "t12" +"origin" "1280 2000 -152" +"classname" "light" +"light" "400" +} +{ +"style" "35" +"spawnflags" "1" +"targetname" "t13" +"classname" "light" +"origin" "1280 2496 -216" +"light" "400" +} +{ +"style" "36" +"spawnflags" "1" +"targetname" "t14" +"origin" "784 2496 -280" +"classname" "light" +} +{ +"classname" "light" +"origin" "1368 2584 -488" +"light" "200" +} +{ +"origin" "1368 1944 -488" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "696 2584 -488" +"light" "150" +} +{ +"origin" "1016 2584 -488" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "1016 1944 -488" +"light" "200" +} +{ +"origin" "1368 2272 -488" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "696 2272 -488" +"light" "200" +} +{ +"classname" "light" +"origin" "960 2296 -488" +"light" "200" +} +{ +"light" "200" +"origin" "1032 2352 -488" +"classname" "light" +} +{ +"classname" "light" +"origin" "888 2352 -488" +"light" "200" +} +{ +"light" "200" +"origin" "960 2408 -488" +"classname" "light" +} +{ +"light" "100" +"classname" "light" +"origin" "984 2448 -304" +} +{ +"classname" "light" +"origin" "832 2360 112" +"light" "400" +} +{ +"classname" "light" +"origin" "1144 2448 -488" +} +{ +"origin" "1232 2360 -488" +"classname" "light" +} +{ +"classname" "light" +"origin" "1320 2448 -488" +"light" "200" +} +{ +"light" "200" +"origin" "1232 2536 -488" +"classname" "light" +} +{ +"classname" "light" +"origin" "1232 2136 -488" +} +{ +"origin" "1144 2048 -488" +"classname" "light" +} +{ +"classname" "light" +"origin" "1232 1960 -488" +"light" "200" +} +{ +"light" "200" +"origin" "1320 2048 -488" +"classname" "light" +} +{ +"classname" "light" +"origin" "832 2336 -200" +} +{ +"classname" "func_door_secret" +"angle" "90" +"spawnflags" "2" +"sounds" "3" +"model" "*13" +} +{ +"classname" "func_door_secret" +"angle" "180" +"sounds" "3" +"model" "*14" +} +{ +"classname" "light" +"origin" "552 2480 -56" +"light" "200" +} +{ +"light" "200" +"origin" "544 2296 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "664 2480 -56" +"light" "200" +} +{ +"classname" "func_door" +"targetname" "t4" +"angle" "-2" +"spawnflags" "1" +"sounds" "2" +"model" "*15" +"lip" "7" // svdijk -- added to prevent z-fighting +} +{ +"classname" "trigger_multiple" +"target" "t4" +"health" "1" +"model" "*16" +} +{ +"spawnflags" "2048" +"classname" "func_door" +"angle" "90" +"targetname" "t5" +"wait" "-1" +"sounds" "2" +"model" "*17" +} +{ +"spawnflags" "2048" +"classname" "trigger_once" +"target" "t5" +"model" "*18" +} +{ +"classname" "item_artifact_super_damage" +"origin" "544 2480 -88" +} +{ +"classname" "light" +"origin" "832 2104 -208" +} +{ +"classname" "light" +"origin" "832 2048 -368" +"light" "150" +} +{ +"classname" "light" +"origin" "1120 2464 112" +} +{ +"origin" "1120 2080 112" +"classname" "light" +} +{ +"classname" "light" +"origin" "752 2080 112" +"light" "200" +} +{ +"classname" "light" +"origin" "1048 2280 -72" +} +{ +"classname" "func_button" +"angle" "270" +"target" "t1" +"model" "*19" +} +{ +"classname" "light" +"origin" "1136 1848 -504" +"light" "220" +} +{ +"origin" "1136 1672 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "1008 1672 -504" +"light" "220" +} +{ +"origin" "1008 1848 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "1288 1848 -504" +"light" "220" +} +{ +"origin" "1400 1584 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "1224 1584 -504" +"light" "220" +} +{ +"origin" "1400 1736 -504" +"classname" "light" +"light" "220" +} +{ +"origin" "880 1672 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "744 1672 -504" +"light" "220" +} +{ +"classname" "light" +"origin" "1312 1648 -392" +"light" "220" +} +{ +"light" "170" +"origin" "1312 1520 -392" +"classname" "light" +} +{ +"classname" "light" +"origin" "1200 1760 -392" +"light" "220" +} +{ +"light" "170" +"origin" "1072 1760 -392" +"classname" "light" +} +{ +"classname" "light" +"origin" "944 1760 -392" +"light" "170" +} +{ +"origin" "832 1992 -208" +"classname" "light" +"light" "220" +} +{ +"origin" "744 1832 -504" +"classname" "light" +} +{ +"light" "170" +"origin" "832 1760 -392" +"classname" "light" +} +{ +"light" "220" +"origin" "680 1936 -504" +"classname" "light" +} +{ +"classname" "light" +"origin" "1312 1392 -352" +"light" "170" +} +{ +"light" "170" +"origin" "1312 1264 -288" +"classname" "light" +} +{ +"classname" "light" +"origin" "1312 1136 -232" +} +{ +"origin" "1224 1456 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "1400 1456 -504" +"light" "220" +} +{ +"origin" "1400 1328 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "1224 1328 -504" +"light" "220" +} +{ +"origin" "1224 1200 -504" +"classname" "light" +"light" "220" +} +{ +"classname" "light" +"origin" "1400 1200 -504" +"light" "220" +} +{ +"origin" "1312 960 -208" +"classname" "light" +} +{ +"classname" "trigger_teleport" +"target" "t6" +"model" "*20" +} +{ +"classname" "light" +"origin" "1312 912 -472" +} +{ +"classname" "light" +"origin" "1312 1080 -368" +} +{ +"classname" "light" +"origin" "1128 1064 -504" +"light" "170" +} +{ +"origin" "1128 856 -504" +"classname" "light" +"light" "170" +} +{ +"classname" "light" +"origin" "1496 856 -504" +"light" "170" +} +{ +"origin" "1496 1064 -504" +"classname" "light" +"light" "170" +} +{ +"classname" "light" +"origin" "1312 776 -504" +"light" "170" +} +{ +"spawnflags" "2" +"angle" "90" +"classname" "func_door_secret" +"model" "*21" +} +{ +"origin" "1072 1024 -168" +"classname" "light" +} +{ +"spawnflags" "1" +"height" "400" +"angle" "-1" +"sounds" "1" +"classname" "func_plat" +"model" "*22" +} +{ +"targetname" "t8" +"spawnflags" "2" +"angle" "90" +"classname" "func_door_secret" +"model" "*23" +} +{ +"target" "t8" +"classname" "trigger_multiple" +"model" "*24" +} +{ +"light" "220" +"origin" "792 888 -248" +"classname" "light" +} +{ +"light" "180" +"classname" "light" +"origin" "944 608 -248" +} +{ +"light" "150" +"origin" "792 512 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "792 512 -56" +"light" "150" +} +{ +"classname" "light" +"origin" "624 928 -240" +"light" "220" +} +{ +"light" "220" +"origin" "504 1200 -248" +"classname" "light" +} +{ +"origin" "936 800 -248" +"classname" "light" +"light" "180" +} +{ +"light" "180" +"classname" "light" +"origin" "960 984 -208" +} +{ +"classname" "light" +"origin" "792 512 128" +"light" "150" +} +{ +"spawnflags" "2" +"origin" "944 1008 -272" +"classname" "item_health" +} +{ +"spawnflags" "1792" +"origin" "144 2352 16" +"classname" "weapon_rocketlauncher" +} +{ +"spawnflags" "1792" +"origin" "1216 1040 -432" +"classname" "weapon_grenadelauncher" +} +{ +"spawnflags" "1793" +"origin" "1392 1024 -432" +"classname" "item_rockets" +} +{ +"targetname" "t6" +"origin" "-32 1800 -56" +"classname" "info_teleport_destination" +} +{ +"spawnflags" "1792" +"origin" "832 2448 -368" +"classname" "weapon_supernailgun" +} +{ +"spawnflags" "1792" +"origin" "128 1216 -208" +"classname" "weapon_supershotgun" +} +{ +"origin" "296 2136 -192" +"classname" "item_shells" +} +{ +"spawnflags" "1" +"origin" "1424 904 -432" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "1376 808 -432" +} +{ +"origin" "1176 936 -432" +"classname" "item_health" +} +{ +"spawnflags" "2048" +"target" "t9" +"wait" "-1" +"angle" "0" +"classname" "func_button" +"model" "*25" +} +{ +"spawnflags" "2048" +"target" "t9" +"wait" "-1" +"angle" "90" +"classname" "func_button" +"model" "*26" +} +{ +"spawnflags" "2048" +"target" "t9" +"wait" "-1" +"angle" "270" +"classname" "func_button" +"model" "*27" +} +{ +"target" "t10" +"targetname" "t9" +"count" "3" +"classname" "trigger_counter" +"model" "*28" +} +{ +"message" "You must press the three buttons..." +"spawnflags" "2048" +"sounds" "2" +"wait" "-1" +"targetname" "t10" +"angle" "180" +"classname" "func_door" +"model" "*29" +} +{ +"light" "150" +"origin" "832 1928 -384" +"classname" "light" +} +{ +"style" "33" +"sounds" "3" +"target" "t11" +"classname" "trigger_once" +"model" "*30" +} +{ +"style" "34" +"sounds" "3" +"target" "t12" +"classname" "trigger_once" +"model" "*31" +} +{ +"style" "35" +"sounds" "3" +"target" "t13" +"classname" "trigger_once" +"model" "*32" +} +{ +"style" "36" +"sounds" "3" +"target" "t14" +"classname" "trigger_once" +"model" "*33" +} +{ +"sounds" "1" +"wait" "-1" +"targetname" "t11" +"spawnflags" "1" +"angle" "-2" +"classname" "func_door" +"model" "*34" +} +{ +"targetname" "t12" +"classname" "func_door" +"angle" "-2" +"spawnflags" "1" +"wait" "-1" +"sounds" "1" +"model" "*35" +} +{ +"targetname" "t13" +"sounds" "1" +"wait" "-1" +"spawnflags" "1" +"angle" "-2" +"classname" "func_door" +"model" "*36" +} +{ +"targetname" "t14" +"classname" "func_door" +"angle" "-2" +"spawnflags" "1" +"wait" "-1" +"sounds" "1" +"model" "*37" +} +{ +"angle" "90" +"origin" "1312 880 -248" +"classname" "info_player_deathmatch" +} +{ +"spawnflags" "1" +"origin" "1376 1024 -272" +"classname" "item_spikes" +} +{ +"origin" "1184 992 -272" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "1376 856 -272" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "1256 1704 -432" +"classname" "item_health" +} +{ +"angle" "90" +"origin" "480 48 24" +"classname" "info_player_deathmatch" +} +{ +"angle" "180" +"origin" "528 1888 -168" +"classname" "info_player_deathmatch" +} +{ +"angle" "0" +"origin" "-272 2928 -56" +"classname" "info_player_deathmatch" +} +{ +"angle" "0" +"origin" "832 2048 -152" +"classname" "info_player_deathmatch" +} +{ +"speed" "300" +"message" "This door opens elsewhere..." +"spawnflags" "2048" +"targetname" "t15" +"angle" "270" +"classname" "func_door" +"wait" "-1" +"model" "*38" +} +{ +"spawnflags" "2048" +"target" "t15" +"classname" "trigger_once" +"model" "*39" +} +{ +"spawnflags" "1792" +"origin" "480 576 0" +"classname" "weapon_nailgun" +} +{ +"spawnflags" "1793" +"origin" "464 728 64" +"classname" "item_spikes" +} +{ +"origin" "328 848 -224" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "344 920 -224" +} +{ +"spawnflags" "1" +"origin" "-16 2064 -208" +"classname" "item_health" +} +{ +"spawnflags" "1792" +"origin" "-480 2240 -160" +"classname" "item_rockets" +} +{ +"spawnflags" "1793" +"origin" "-96 2456 16" +"classname" "item_shells" +} +{ +"classname" "item_rockets" +"origin" "-104 2216 16" +"spawnflags" "1793" +} +{ +"classname" "item_artifact_invulnerability" +"origin" "256 1808 -40" +"spawnflags" "1792" +} +{ +"classname" "monster_army" +"origin" "0 576 24" +"angle" "0" +"spawnflags" "256" +} +{ +"classname" "monster_army" +"origin" "8 1520 -200" +"angle" "270" +} +{ +"classname" "monster_dog" +"origin" "88 1520 -200" +"angle" "270" +} +{ +"classname" "monster_army" +"origin" "224 1552 -200" +"angle" "270" +"spawnflags" "768" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "-8 936 -200" +"classname" "monster_army" +} +{ +"classname" "monster_army" +"origin" "648 736 104" +"spawnflags" "768" +"angle" "180" +} +{ +"classname" "item_artifact_envirosuit" +"origin" "712 2040 -408" +"angle" "90" +} +{ +"classname" "light" +"origin" "712 2040 -360" +"light" "100" +} +{ +"classname" "item_rockets" +"origin" "1328 2536 -528" +"spawnflags" "1793" +} +{ +"classname" "item_health" +"origin" "916 2416 -136" +"spawnflags" "2" +} +{ +"spawnflags" "1" +"classname" "monster_army" +"origin" "1312 936 -248" +"angle" "90" +} +{ +"classname" "monster_dog" +"origin" "1336 1784 -408" +"angle" "180" +"spawnflags" "257" +} +{ +"spawnflags" "257" +"angle" "90" +"origin" "1392 928 -248" +"classname" "monster_army" +} +{ +"classname" "monster_army" +"origin" "1384 1008 -248" +"angle" "90" +"spawnflags" "768" +} +{ +"spawnflags" "768" +"angle" "90" +"origin" "1240 1008 -248" +"classname" "monster_army" +} +{ +"classname" "monster_army" +"origin" "1256 1760 -408" +"angle" "180" +"spawnflags" "257" +} +{ +"classname" "monster_army" +"origin" "824 1784 -408" +"spawnflags" "257" +"angle" "90" +} +{ +"classname" "monster_dog" +"origin" "1128 1760 -408" +"angle" "180" +"spawnflags" "769" +} +{ +"classname" "path_corner" +"origin" "880 2048 -168" +"target" "t16" +"targetname" "t17" +} +{ +"origin" "1232 2048 -232" +"classname" "path_corner" +"targetname" "t16" +"target" "t17" +} +{ +"classname" "monster_army" +"origin" "1232 2088 -216" +"target" "t16" +} +{ +"classname" "monster_army" +"origin" "1232 2448 -280" +"angle" "270" +"spawnflags" "256" +} +{ +"classname" "monster_army" +"origin" "832 2464 -344" +"angle" "0" +"spawnflags" "256" +} +{ +"classname" "monster_army" +"origin" "832 2072 -408" +"angle" "90" +} +{ +"classname" "monster_dog" +"origin" "840 1960 -408" +"angle" "90" +"spawnflags" "768" +} +{ +"classname" "trigger_multiple" +"target" "t18" +"health" "1" +"model" "*40" +} +{ +"classname" "func_door_secret" +"angle" "90" +"spawnflags" "2" +"targetname" "t18" +"model" "*41" +} +{ +"classname" "weapon_supershotgun" +"origin" "-360 2912 -80" +} +{ +"classname" "trigger_multiple" +"target" "t18" +"model" "*42" +} +{ +"classname" "light" +"origin" "-352 2912 -24" +"light" "120" +} +{ +"classname" "light" +"origin" "160 3024 0" +"light" "120" +} +{ +"classname" "item_shells" +"origin" "528 720 80" +} +{ +"classname" "monster_army" +"origin" "416 1912 -168" +"angle" "180" +"spawnflags" "768" +} +{ +"classname" "monster_dog" +"origin" "432 2120 -168" +"angle" "180" +"spawnflags" "256" +} +{ +"classname" "path_corner" +"origin" "248 1992 -200" +"targetname" "t19" +"target" "t20" +} +{ +"origin" "-200 1992 -200" +"classname" "path_corner" +"targetname" "t20" +"target" "t21" +} +{ +"classname" "path_corner" +"origin" "-136 1912 -200" +"targetname" "t21" +"target" "t22" +} +{ +"origin" "248 1912 -200" +"classname" "path_corner" +"target" "t19" +"targetname" "t22" +} +{ +"classname" "monster_army" +"origin" "80 2024 -184" +"target" "t20" +} +{ +"classname" "monster_army" +"origin" "-16 1888 -184" +"spawnflags" "256" +"target" "t22" +} +{ +"classname" "monster_dog" +"origin" "-248 2144 -136" +"spawnflags" "768" +"angle" "315" +} +{ +"classname" "path_corner" +"origin" "-560 2352 40" +"targetname" "t23" +"target" "t24" +} +{ +"origin" "-104 2352 40" +"classname" "path_corner" +"target" "t23" +"targetname" "t24" +} +{ +"classname" "monster_army" +"origin" "-432 2352 56" +"spawnflags" "768" +"target" "t23" +} +{ +"angle" "0" +"classname" "monster_dog" +"origin" "-544 2584 56" +"spawnflags" "256" +} +{ +"classname" "monster_army" +"origin" "-344 2656 -104" +"angle" "270" +} +{ +"classname" "monster_dog" +"origin" "-72 2896 -56" +"spawnflags" "256" +"angle" "225" +} +{ +"classname" "monster_army" +"origin" "432 2920 -56" +"target" "t25" +} +{ +"classname" "monster_army" +"origin" "424 2832 -56" +"spawnflags" "256" +"angle" "180" +} +{ +"classname" "path_corner" +"origin" "368 2936 -72" +"targetname" "t25" +"target" "t26" +} +{ +"origin" "368 2696 -72" +"classname" "path_corner" +"targetname" "t26" +"target" "t27" +} +{ +"classname" "path_corner" +"origin" "480 2696 -72" +"targetname" "t27" +"target" "t28" +} +{ +"origin" "480 2936 -72" +"classname" "path_corner" +"target" "t25" +"targetname" "t28" +} +{ +"classname" "monster_army" +"origin" "424 2672 -56" +"target" "t27" +} +{ +"classname" "monster_army" +"origin" "424 2880 -56" +"angle" "180" +"spawnflags" "768" +} +{ +"classname" "monster_army" +"origin" "424 2760 -56" +"spawnflags" "768" +"angle" "180" +} +{ +"classname" "path_corner" +"origin" "832 2712 -88" +"targetname" "t29" +"target" "t30" +} +{ +"origin" "832 2416 -104" +"classname" "path_corner" +"target" "t29" +"targetname" "t30" +} +{ +"classname" "monster_army" +"origin" "848 2584 -72" +"spawnflags" "257" +"target" "t29" +} +{ +"classname" "monster_army" +"origin" "824 2008 -152" +"angle" "90" +"spawnflags" "768" +} +{ +"classname" "item_health" +"origin" "-376 1704 -224" +"spawnflags" "1" +} +{ +"angle" "180" +"spawnflags" "768" +"origin" "248 2352 40" +"classname" "monster_army" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "-72 2464 40" +"classname" "monster_army" +} +{ +"spawnflags" "768" +"angle" "225" +"origin" "904 1024 -248" +"classname" "monster_army" +} +{ +"light" "100" +"style" "10" +"classname" "light" +"origin" "688 0 80" +} +{ +"message" "Shoot this secret door..." +"spawnflags" "1" +"angle" "0" +"classname" "func_door_secret" +"model" "*43" +} +{ +"origin" "672 -40 48" +"classname" "item_shells" +} +{ +"classname" "trigger_secret" +"model" "*44" +} +{ +"classname" "trigger_secret" +"model" "*45" +} +{ +"classname" "trigger_secret" +"model" "*46" +} +{ +"classname" "trigger_secret" +"model" "*47" +} +{ +"classname" "trigger_secret" +"model" "*48" +} +{ +"classname" "trigger_secret" +"model" "*49" +} +{ +"light" "100" +"origin" "0 632 -88" +"classname" "light" +} +{ +"classname" "item_health" +"origin" "600 2200 -128" +"spawnflags" "1" +} +{ +"light" "220" +"classname" "light" +"origin" "832 1880 -504" +} +{ +"origin" "72 2056 -208" +"classname" "misc_explobox" +} +{ +"light" "200" +"origin" "-128 584 72" +"classname" "light" +} +{ +"light" "200" +"origin" "-128 568 -136" +"classname" "light" +} +{ +"light" "100" +"origin" "-56 632 -168" +"classname" "light" +} +{ +"light" "200" +"origin" "-56 864 -136" +"classname" "light" +} +{ +"light" "200" +"origin" "40 1672 -40" +"classname" "light" +} +{ +"classname" "light" +"origin" "216 1672 -40" +"light" "200" +} +{ +"classname" "light" +"origin" "128 1080 -152" +"light" "200" +} +{ +"light" "200" +"origin" "128 1096 72" +"classname" "light" +} +{ +"light" "250" +"classname" "light" +"origin" "-352 1656 72" +} +{ +"origin" "608 1640 72" +"classname" "light" +"light" "250" +} +{ +"origin" "-48 1144 -320" +"classname" "light" +"light" "170" +} +{ +"light" "170" +"classname" "light" +"origin" "-48 1256 -320" +} +{ +"origin" "320 1256 -320" +"classname" "light" +"light" "170" +} +{ +"light" "170" +"classname" "light" +"origin" "312 1128 -320" +} +{ +"origin" "136 1128 -320" +"classname" "light" +"light" "170" +} +{ +"light" "170" +"classname" "light" +"origin" "136 1272 -320" +} +{ +"spawnflags" "3072" +"wait" "5" +"sounds" "2" +"message" "You can jump across..." +"classname" "trigger_multiple" +"targetname" "t32" +"model" "*50" +} +{ +"spawnflags" "3072" +"wait" "5" +"message" "You can jump up here..." +"sounds" "2" +"classname" "trigger_multiple" +"targetname" "t31" +"model" "*51" +} +{ +"light" "150" +"origin" "1008 2128 -408" +"classname" "light" +} +{ +"light" "250" +"origin" "1312 544 -184" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "1208 456 -184" +} +{ +"origin" "1416 456 -184" +"classname" "light" +"light" "200" +} +{ +"light" "170" +"origin" "1312 728 -56" +"classname" "light" +} +{ +"map" "e1m2" +"classname" "trigger_changelevel" +"model" "*52" +} +{ +"classname" "item_health" +"origin" "1224 2464 -304" +"spawnflags" "1" +} +{ +"classname" "light" +"origin" "688 1680 -160" +"light" "160" +} +{ +"light" "160" +"origin" "-392 1688 -160" +"classname" "light" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "288 1536 -200" +"classname" "monster_army" +} +{ +"spawnflags" "768" +"origin" "968 2432 -112" +"classname" "monster_army" +} +{ +"wait" "5" +"message" "Walk into the slipgate to exit." +"classname" "trigger_multiple" +"sounds" "2" +"angle" "270" +"model" "*53" +} +{ +"classname" "trigger_once" +"killtarget" "t31" +"target" "t31" +"spawnflags" "3072" +"model" "*54" +} +{ +"classname" "trigger_once" +"spawnflags" "3072" +"target" "t32" +"killtarget" "t32" +"model" "*55" +} +{ +"classname" "item_armor2" +"origin" "1312 1048 -432" +} +{ +"classname" "ambient_comp_hum" +"origin" "250 194 72" +} +{ +"origin" "714 194 72" +"classname" "ambient_comp_hum" +} +{ +"classname" "ambient_comp_hum" +"origin" "626 2058 -104" +} +{ +"origin" "466 2226 -104" +"classname" "ambient_comp_hum" +} +{ +"classname" "info_intermission" +"origin" "-112 704 56" +"mangle" "20 45 0" +} +{ +"classname" "info_intermission" +"origin" "-208 2736 192" +"mangle" "20 225 0" +} +{ +"classname" "info_intermission" +"origin" "240 2664 104" +"mangle" "20 120 0" +} +{ +"classname" "info_intermission" +"origin" "1376 1936 64" +"mangle" "20 135 0" +} +{ +"angle" "90" +"origin" "528 -296 72" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "432 -296 72" +"angle" "90" +} +{ +"angle" "90" +"origin" "480 -240 72" +"classname" "info_player_coop" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*56" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*57" +} +{ +"classname" "ambient_drone" +"origin" "1314 450 -200" +} diff --git a/Misc/qs_pak/maps/e1m2.diff b/Misc/qs_pak/maps/e1m2.diff new file mode 100644 index 0000000..4080898 --- /dev/null +++ b/Misc/qs_pak/maps/e1m2.diff @@ -0,0 +1,28 @@ +--- e1m2.ent ++++ e1m2@0caa.ent +@@ -1567,23 +1567,23 @@ + { + "light" "200" + "origin" "-612 -500 548" + "classname" "light" + } + { + "classname" "func_door" +-"angle" "90" ++"angle" "91" // svdijk -- changed to prevent z-fighting (was "90") + "targetname" "t110" + "wait" "-1" + "model" "*33" + } + { + "sounds" "3" + "classname" "func_door" +-"angle" "270" ++"angle" "269" // svdijk -- changed to prevent z-fighting (was "270") + "wait" "-1" + "model" "*34" + } + { + "classname" "trigger_once" + "target" "t110" + "model" "*35" diff --git a/Misc/qs_pak/maps/e1m2@0caa.ent b/Misc/qs_pak/maps/e1m2@0caa.ent new file mode 100644 index 0000000..2e04e9f --- /dev/null +++ b/Misc/qs_pak/maps/e1m2@0caa.ent @@ -0,0 +1,3019 @@ +{ +"message" "Castle of the Damned" +"wad" "gfx/wizard.wad" +"classname" "worldspawn" +"worldtype" "0" +"sounds" "8" +} +{ +"angle" "270" +"origin" "1496 1664 296" +"classname" "info_player_start" +} +{ +"origin" "1432 672 336" +"classname" "light" +"light" "250" +} +{ +"light" "200" +"origin" "1496 888 272" +"classname" "light" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "932 640 340" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1104 812 340" +} +{ +"classname" "light" +"origin" "1104 640 544" +"light" "300" +} +{ +"light" "175" +"origin" "1216 536 353" +"classname" "light" +} +{ +"light" "250" +"origin" "1816 328 448" +"classname" "light" +} +{ +"light" "200" +"origin" "1632 472 208" +"classname" "light" +} +{ +"light" "200" +"origin" "1792 -392 240" +"classname" "light" +} +{ +"light" "200" +"origin" "1452 -124 508" +"classname" "light" +} +{ +"light" "150" +"origin" "1196 -124 508" +"classname" "light" +} +{ +"light" "150" +"origin" "1044 -124 508" +"classname" "light" +} +{ +"light" "200" +"origin" "756 -124 508" +"classname" "light" +} +{ +"light" "250" +"origin" "744 336 145" +"classname" "light" +} +{ +"light" "200" +"origin" "1176 -912 672" +"classname" "light" +} +{ +"origin" "1328 -544 552" +"classname" "light" +} +{ +"classname" "light" +"origin" "1528 -912 640" +"light" "200" +} +{ +"light" "200" +"classname" "light" +"origin" "880 -648 672" +} +{ +"origin" "240 -264 392" +"classname" "light" +"light" "250" +} +{ +"classname" "light" +"origin" "-352 -504 464" +"light" "200" +} +{ +"origin" "-448 -608 804" +"classname" "light" +"light" "450" +} +{ +"light" "250" +"origin" "776 -912 472" +"classname" "light" +} +{ +"light" "300" +"origin" "1630 -806 428" +"classname" "light_torch_small_walltorch" +} +{ +"light" "150" +"origin" "1528 -912 464" +"classname" "light" +} +{ +"classname" "light" +"origin" "1180 -484 560" +} +{ +"classname" "light" +"origin" "1184 -612 560" +} +{ +"classname" "light" +"origin" "1016 -368 472" +"light" "100" +} +{ +"classname" "light" +"origin" "1016 -464 472" +"light" "100" +} +{ +"classname" "light" +"origin" "1020 -560 472" +"light" "100" +} +{ +"classname" "light" +"origin" "1208 -776 472" +"light" "100" +} +{ +"classname" "light" +"origin" "1288 -776 472" +"light" "100" +} +{ +"classname" "light" +"origin" "1360 -776 472" +"light" "100" +} +{ +"light" "200" +"origin" "1792 120 376" +"classname" "light" +} +{ +"origin" "1538 182 356" +"classname" "light_torch_small_walltorch" +} +{ +"light" "200" +"origin" "1640 80 360" +"classname" "light" +} +{ +"light" "200" +"origin" "1928 80 360" +"classname" "light" +} +{ +"light" "250" +"origin" "1792 296 208" +"classname" "light" +} +{ +"light" "150" +"origin" "1800 40 160" +"classname" "light" +} +{ +"light" "200" +"origin" "1776 -392 160" +"classname" "light" +} +{ +"light" "200" +"origin" "1304 -392 152" +"classname" "light" +} +{ +"light" "250" +"origin" "1632 112 136" +"classname" "light" +} +{ +"light" "250" +"origin" "1432 312 136" +"classname" "light" +} +{ +"light" "200" +"origin" "1136 -656 160" +"classname" "light" +} +{ +"light" "200" +"origin" "1136 -416 160" +"classname" "light" +} +{ +"light" "250" +"origin" "1448 -552 160" +"classname" "light" +} +{ +"light" "200" +"origin" "1920 440 136" +"classname" "light" +} +{ +"light" "200" +"origin" "968 88 177" +"classname" "light" +} +{ +"light" "300" +"origin" "1088 312 129" +"classname" "light" +} +{ +"light" "150" +"origin" "1376 168 129" +"classname" "light" +} +{ +"light" "250" +"origin" "112 -384 392" +"classname" "light" +} +{ +"origin" "300 -1004 508" +"classname" "light" +} +{ +"origin" "296 -812 505" +"classname" "light" +} +{ +"origin" "300 -1204 505" +"classname" "light" +} +{ +"light" "150" +"origin" "470 -1006 468" +"classname" "light_torch_small_walltorch" +} +{ +"light" "250" +"origin" "984 -1216 496" +"classname" "light" +} +{ +"light" "250" +"origin" "888 -1128 552" +"classname" "light" +} +{ +"light" "200" +"origin" "800 -1216 592" +"classname" "light" +} +{ +"light" "200" +"origin" "664 -1216 592" +"classname" "light" +} +{ +"light" "200" +"origin" "584 -1136 592" +"classname" "light" +} +{ +"light" "200" +"origin" "584 -968 592" +"classname" "light" +} +{ +"light" "250" +"origin" "584 -744 592" +"classname" "light" +} +{ +"light" "200" +"origin" "528 -1144 464" +"classname" "light" +} +{ +"light" "200" +"origin" "528 -856 464" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "1496 1544 440" +} +{ +"origin" "1384 1392 440" +"classname" "light" +"light" "250" +} +{ +"origin" "1496 1104 520" +"classname" "light" +} +{ +"origin" "1608 1400 440" +"classname" "light" +"light" "250" +} +{ +"light" "250" +"origin" "1240 1712 360" +"classname" "light" +} +{ +"light" "250" +"origin" "1744 1696 360" +"classname" "light" +} +{ +"classname" "light" +"origin" "1384 1136 440" +"light" "250" +} +{ +"classname" "light" +"origin" "1608 1144 440" +"light" "250" +} +{ +"classname" "path_corner" +"origin" "1168 736 296" +"targetname" "t5" +"target" "t6" +} +{ +"classname" "path_corner" +"origin" "992 744 296" +"targetname" "t6" +"target" "t7" +} +{ +"classname" "path_corner" +"origin" "1000 544 296" +"targetname" "t7" +"target" "t34" +} +{ +"classname" "item_health" +"origin" "960 704 288" +} +{ +"classname" "item_shells" +"origin" "952 512 288" +} +{ +"classname" "path_corner" +"origin" "1344 -128 304" +"targetname" "t9" +"target" "t8" +} +{ +"classname" "path_corner" +"origin" "898 -128 304" +"targetname" "t8" +"target" "t9" +} +{ +"spawnflags" "1" +"classname" "monster_ogre" +"origin" "1018 -126 320" +"angle" "0" +"target" "t8" +} +{ +"classname" "item_health" +"origin" "1344 -224 296" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "1400 -224 296" +"spawnflags" "1" +} +{ +"origin" "1528 192 296" +"classname" "item_shells" +} +{ +"classname" "path_corner" +"origin" "1496 1040 184" +"targetname" "t22" +"target" "t23" +} +{ +"classname" "path_corner" +"origin" "1496 840 248" +"targetname" "t23" +"target" "t33" +} +{ +"spawnflags" "1" +"classname" "item_shells" +"origin" "1056 -648 288" +} +{ +"classname" "item_health" +"origin" "1184 -736 288" +} +{ +"spawnflags" "257" +"classname" "monster_army" +"origin" "1646 -698 360" +"angle" "180" +"targetname" "t89" +} +{ +"classname" "path_corner" +"origin" "1400 640 272" +"targetname" "t30" +"target" "t79" +} +{ +"classname" "path_corner" +"origin" "1496 752 232" +"targetname" "t33" +"target" "t77" +} +{ +"classname" "path_corner" +"origin" "1192 560 296" +"targetname" "t34" +"target" "t80" +} +{ +"classname" "item_shells" +"origin" "1616 1280 176" +} +{ +"classname" "item_health" +"origin" "1056 -840 416" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "1104 -840 416" +"spawnflags" "1" +} +{ +"spawnflags" "1" +"classname" "monster_army" +"origin" "262 -458 320" +"angle" "0" +"target" "t96" +} +{ +"spawnflags" "1024" +"classname" "item_health" +"origin" "136 -296 296" +} +{ +"classname" "path_corner" +"origin" "-536 -704 472" +"targetname" "t42" +"target" "t41" +} +{ +"classname" "path_corner" +"origin" "-576 -416 472" +"targetname" "t41" +"target" "t42" +} +{ +"classname" "monster_knight" +"origin" "-578 -654 480" +"target" "t41" +"spawnflags" "1" +} +{ +"classname" "item_shells" +"origin" "-368 -752 456" +} +{ +"classname" "item_health" +"origin" "-16 -520 360" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "-16 -576 360" +"spawnflags" "1" +} +{ +"classname" "light" +"origin" "1848 -568 320" +"light" "200" +} +{ +"classname" "light" +"origin" "1760 -560 408" +"light" "200" +} +{ +"classname" "light" +"origin" "1624 -560 352" +"light" "150" +} +{ +"targetname" "t43" +"angle" "270" +"origin" "800 368 312" +"classname" "info_teleport_destination" +} +{ +"origin" "752 168 296" +"classname" "item_health" +} +{ +"target" "t43" +"classname" "trigger_teleport" +"model" "*1" +} +{ +"origin" "1712 -568 256" +"classname" "item_health" +} +{ +"classname" "monster_ogre" +"origin" "1494 1134 208" +"angle" "270" +"target" "t22" +} +{ +"light" "300" +"origin" "1856 1288 384" +"classname" "light" +} +{ +"classname" "light" +"origin" "1136 1288 384" +} +{ +"light" "200" +"origin" "1920 328 380" +"classname" "light" +} +{ +"target" "t122" +"spawnflags" "2048" +"sounds" "1" +"classname" "item_key1" +"origin" "880 -300 464" +} +{ +"light" "300" +"origin" "648 -384 430" +"classname" "light_flame_small_yellow" +} +{ +"light" "250" +"classname" "light_flame_small_yellow" +"origin" "1104 -224 406" +} +{ +"light" "250" +"origin" "1456 -128 406" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light" +"origin" "988 532 353" +"light" "175" +} +{ +"light" "125" +"origin" "1100 648 328" +"classname" "light" +} +{ +"origin" "1616 936 310" +"classname" "light_flame_small_yellow" +"light" "300" +} +{ +"light" "300" +"classname" "light_flame_small_yellow" +"origin" "1360 936 310" +} +{ +"origin" "1792 504 390" +"classname" "light_flame_small_yellow" +"light" "300" +} +{ +"origin" "1972 -252 332" +"classname" "info_null" +"targetname" "t47" +} +{ +"light" "800" +"origin" "1992 -252 336" +"classname" "light" +"target" "t47" +} +{ +"classname" "info_null" +"origin" "1948 -292 332" +"targetname" "t48" +} +{ +"light" "800" +"classname" "light" +"origin" "1948 -312 336" +"target" "t48" +} +{ +"origin" "880 -328 562" +"classname" "light_flame_small_yellow" +"light" "300" +} +{ +"classname" "light" +"origin" "1056 -1288 504" +} +{ +"origin" "1184 -1288 504" +"classname" "light" +} +{ +"classname" "light" +"origin" "1312 -1288 504" +} +{ +"origin" "1440 -1288 504" +"classname" "light" +} +{ +"sounds" "1" +"classname" "func_door" +"angle" "-2" +"wait" "-1" +"targetname" "t50" +"model" "*2" +} +{ +"sounds" "1" +"classname" "func_door" +"wait" "-1" +"angle" "-2" +"targetname" "t50" +"model" "*3" +} +{ +"classname" "trigger_once" +"target" "t50" +"model" "*4" +} +{ +"classname" "light" +"origin" "1368 -1016 504" +"light" "200" +} +{ +"light" "200" +"origin" "1120 -1024 504" +"classname" "light" +} +{ +"classname" "light" +"origin" "1248 -1184 464" +"light" "175" +} +{ +"classname" "light" +"origin" "776 -480 480" +"light" "225" +} +{ +"classname" "light" +"origin" "1904 -144 168" +"light" "200" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1706 -206 316" +"light" "300" +} +{ +"light" "300" +"origin" "2134 -34 316" +"classname" "light_torch_small_walltorch" +} +{ +"origin" "1152 -296 422" +"classname" "light_flame_small_yellow" +"light" "250" +} +{ +"light" "250" +"classname" "light_flame_small_yellow" +"origin" "1152 -760 422" +} +{ +"origin" "1528 -556 478" +"classname" "light_flame_small_yellow" +"light" "250" +} +{ +"targetname" "t52" +"origin" "1532 -552 328" +"classname" "info_null" +} +{ +"origin" "1340 -544 384" +"classname" "item_armor2" +} +{ +"sounds" "1" +"targetname" "t53" +"lip" "64" +"wait" "-1" +"angle" "-1" +"classname" "func_door" +"model" "*5" +} +{ +"sounds" "1" +"targetname" "t53" +"classname" "func_door" +"angle" "-1" +"wait" "-1" +"lip" "64" +"model" "*6" +} +{ +"targetname" "t53" +"target" "t54" +"classname" "trigger_teleport" +"spawnflags" "2" +"model" "*7" +} +{ +"targetname" "t54" +"angle" "180" +"origin" "1408 -688 449" +"classname" "info_teleport_destination" +} +{ +"targetname" "t53" +"target" "t57" +"classname" "trigger_teleport" +"spawnflags" "2" +"model" "*8" +} +{ +"targetname" "t57" +"angle" "180" +"origin" "1408 -400 361" +"classname" "info_teleport_destination" +} +{ +"spawnflags" "768" +"targetname" "t53" +"angle" "180" +"origin" "1912 -856 217" +"classname" "monster_wizard" +} +{ +"spawnflags" "768" +"targetname" "t53" +"classname" "monster_wizard" +"origin" "1912 -936 217" +"angle" "180" +} +{ +"targetname" "t50" +"angle" "90" +"origin" "1320 -1112 441" +"classname" "monster_knight" +} +{ +"spawnflags" "256" +"targetname" "t50" +"angle" "0" +"origin" "1056 -1144 441" +"classname" "monster_knight" +} +{ +"sounds" "1" +"targetname" "t61" +"wait" "-1" +"angle" "-2" +"classname" "func_door" +"model" "*9" +} +{ +"sounds" "3" +"lip" "64" +"spawnflags" "1" +"targetname" "t58" +"angle" "270" +"wait" "-1" +"classname" "func_door" +"model" "*10" +} +{ +"sounds" "1" +"wait" "-1" +"angle" "270" +"target" "t58" +"classname" "func_button" +"model" "*11" +} +{ +"target" "t61" +"classname" "trigger_once" +"model" "*12" +} +{ +"light" "225" +"origin" "984 -480 480" +"classname" "light" +} +{ +"light" "175" +"origin" "880 -368 176" +"classname" "light" +} +{ +"classname" "light" +"origin" "880 -592 240" +"light" "175" +} +{ +"light" "200" +"origin" "880 -488 184" +"classname" "light" +} +{ +"light" "150" +"origin" "880 -304 472" +"classname" "light" +} +{ +"classname" "light" +"origin" "-96 308 864" +"light" "850" +} +{ +"origin" "-32 -440 624" +"classname" "light" +} +{ +"sounds" "1" +"targetname" "t73" +"wait" "-1" +"lip" "196" +"angle" "-1" +"classname" "func_door" +"model" "*13" +} +{ +"light" "300" +"origin" "104 144 688" +"classname" "light" +} +{ +"classname" "light" +"origin" "-264 144 688" +"light" "300" +} +{ +"sounds" "1" +"targetname" "t73" +"wait" "-1" +"classname" "func_door" +"angle" "-1" +"lip" "196" +"model" "*14" +} +{ +"classname" "light_flame_small_yellow" +"origin" "-24 -232 414" +"light" "250" +} +{ +"lip" "-2" +"sounds" "3" +"speed" "350" +"targetname" "t73" +"angle" "180" +"wait" "-1" +"classname" "func_door" +"model" "*15" +} +{ +"target" "t63" +"targetname" "t62" +"origin" "-12 312 264" +"classname" "path_corner" +} +{ +"target" "t64" +"targetname" "t63" +"origin" "-12 312 356" +"classname" "path_corner" +} +{ +"wait" "-1" +"target" "t66" +"targetname" "t64" +"classname" "path_corner" +"origin" "-13 440 355" +} +{ +"sounds" "1" +"targetname" "t71" +"wait" "-1" +"target" "t65" +"angle" "-2" +"classname" "func_button" +"model" "*16" +} +{ +"target" "t64" +"targetname" "t66" +"origin" "-13 440 355" +"classname" "path_corner" +} +{ +"light" "200" +"origin" "-96 440 376" +"classname" "light" +} +{ +"light" "150" +"origin" "8 456 376" +"classname" "light" +} +{ +"targetname" "t70" +"target" "t67" +"classname" "path_corner" +"origin" "-220 312 264" +} +{ +"target" "t68" +"targetname" "t67" +"classname" "path_corner" +"origin" "-220 312 356" +} +{ +"wait" "-1" +"target" "t69" +"targetname" "t68" +"origin" "-221 440 355" +"classname" "path_corner" +} +{ +"target" "t68" +"targetname" "t69" +"classname" "path_corner" +"origin" "-221 440 355" +} +{ +"classname" "light" +"origin" "-200 456 376" +"light" "150" +} +{ +"targetname" "t65" +"target" "t62" +"classname" "func_train" +"speed" "50" +"sounds" "1" +"model" "*17" +} +{ +"light" "250" +"origin" "-96 632 406" +"classname" "light_flame_small_yellow" +} +{ +"targetname" "t72" +"origin" "-96 288 304" +"classname" "info_null" +} +{ +"light" "450" +"target" "t72" +"origin" "-96 288 368" +"classname" "light" +} +{ +"target" "t70" +"targetname" "t65" +"speed" "50" +"classname" "func_train" +"sounds" "1" +"model" "*18" +} +{ +"lip" "-2" +"sounds" "0" +"speed" "350" +"classname" "func_door" +"wait" "-1" +"angle" "0" +"model" "*19" +} +{ +"targetname" "t65" +"delay" "4.7" +"target" "t73" +"classname" "trigger_once" +"model" "*20" +} +{ +"targetname" "t73" +"angle" "270" +"origin" "-96 552 320" +"classname" "monster_demon1" +"spawnflags" "1024" +} +{ +"targetname" "t74" +"angle" "90" +"origin" "132 -192 476" +"classname" "info_teleport_destination" +} +{ +"targetname" "t75" +"classname" "info_teleport_destination" +"origin" "-328 -196 476" +"angle" "90" +} +{ +"target" "t75" +"classname" "trigger_teleport" +"spawnflags" "1" +"model" "*21" +} +{ +"target" "t74" +"classname" "trigger_teleport" +"spawnflags" "1" +"model" "*22" +} +{ +"light" "200" +"origin" "-418 306 356" +"classname" "light" +} +{ +"classname" "light" +"origin" "260 308 356" +"light" "200" +} +{ +"sounds" "0" +"targetname" "t73" +"wait" "-1" +"angle" "180" +"classname" "func_door" +"model" "*23" +} +{ +"sounds" "0" +"targetname" "t73" +"wait" "-1" +"angle" "0" +"classname" "func_door" +"model" "*24" +} +{ +"sounds" "0" +"wait" "-1" +"angle" "0" +"targetname" "t73" +"classname" "func_door" +"model" "*25" +} +{ +"sounds" "0" +"targetname" "t73" +"angle" "180" +"wait" "-1" +"classname" "func_door" +"model" "*26" +} +{ +"sounds" "3" +"wait" "-1" +"angle" "-2" +"targetname" "t73" +"classname" "func_door" +"model" "*27" +} +{ +"classname" "light" +"origin" "-96 24 360" +"light" "100" +} +{ +"light" "100" +"origin" "-96 -40 360" +"classname" "light" +} +{ +"classname" "light" +"origin" "-160 -568 624" +} +{ +"origin" "-160 -440 624" +"classname" "light" +} +{ +"classname" "light" +"origin" "-32 -568 624" +} +{ +"classname" "light" +"origin" "-96 -88 484" +"light" "150" +} +{ +"classname" "light" +"origin" "-440 -408 804" +"light" "450" +} +{ +"classname" "light" +"origin" "600 -128 352" +"light" "200" +} +{ +"classname" "light" +"origin" "576 -608 504" +"light" "250" +} +{ +"classname" "light" +"origin" "384 -504 392" +"light" "250" +} +{ +"classname" "light" +"origin" "1264 240 295" +"light" "250" +} +{ +"light" "250" +"origin" "944 240 295" +"classname" "light" +} +{ +"classname" "path_corner" +"origin" "1480 704 264" +"targetname" "t77" +"target" "t78" +} +{ +"classname" "path_corner" +"origin" "1448 656 264" +"targetname" "t78" +"target" "t30" +} +{ +"classname" "path_corner" +"origin" "1264 640 304" +"targetname" "t80" +"target" "t5" +} +{ +"classname" "path_corner" +"origin" "1328 640 304" +"targetname" "t79" +"target" "t80" +} +{ +"light" "200" +"origin" "1488 -392 216" +"classname" "light" +} +{ +"classname" "path_corner" +"origin" "816 80 304" +"targetname" "t83" +"target" "t82" +"spawnflags" "256" +} +{ +"origin" "816 312 304" +"classname" "path_corner" +"targetname" "t82" +"target" "t83" +"spawnflags" "256" +} +{ +"classname" "monster_army" +"origin" "806 206 320" +"angle" "90" +"target" "t82" +"spawnflags" "256" +} +{ +"classname" "trigger_once" +"target" "t84" +"model" "*28" +} +{ +"classname" "monster_ogre" +"origin" "1790 -146 312" +"angle" "90" +"targetname" "t84" +} +{ +"classname" "path_corner" +"origin" "1088 -672 296" +"target" "t85" +"targetname" "t88" +} +{ +"origin" "1088 -376 296" +"classname" "path_corner" +"targetname" "t85" +"target" "t86" +} +{ +"classname" "path_corner" +"origin" "1088 -376 296" +"targetname" "t87" +"target" "t88" +} +{ +"origin" "1448 -376 296" +"classname" "path_corner" +"targetname" "t86" +"target" "t87" +} +{ +"spawnflags" "1" +"classname" "monster_ogre" +"origin" "1086 -498 312" +"angle" "270" +"target" "t88" +} +{ +"spawnflags" "256" +"classname" "trigger_once" +"target" "t89" +"model" "*29" +} +{ +"classname" "item_health" +"origin" "352 -752 408" +"spawnflags" "1025" +} +{ +"spawnflags" "1025" +"origin" "352 -792 408" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "352 -832 408" +"spawnflags" "1" +} +{ +"classname" "path_corner" +"origin" "408 -776 416" +"targetname" "t94" +"target" "t95" +} +{ +"origin" "400 -1088 416" +"classname" "path_corner" +"targetname" "t95" +"target" "t94" +} +{ +"classname" "path_corner" +"origin" "584 -1096 416" +"targetname" "t92" +"target" "t93" +} +{ +"origin" "584 -792 416" +"classname" "path_corner" +"targetname" "t93" +"target" "t92" +} +{ +"classname" "monster_army" +"origin" "390 -970 432" +"angle" "0" +"target" "t94" +} +{ +"classname" "monster_army" +"origin" "566 -970 432" +"angle" "270" +"target" "t92" +} +{ +"classname" "path_corner" +"origin" "208 -304 304" +"targetname" "t97" +"target" "t96" +} +{ +"classname" "path_corner" +"origin" "208 -464 304" +"targetname" "t96" +"target" "t97" +} +{ +"spawnflags" "1280" +"classname" "path_corner" +"origin" "-344 160 304" +"targetname" "t100" +"target" "t99" +} +{ +"spawnflags" "1280" +"origin" "168 152 304" +"classname" "path_corner" +"targetname" "t99" +"target" "t100" +} +{ +"spawnflags" "1280" +"classname" "monster_ogre" +"origin" "240 152 320" +"angle" "180" +"target" "t99" +} +{ +"spawnflags" "768" +"classname" "monster_ogre" +"origin" "-392 80 320" +"angle" "0" +"targetname" "t101" +} +{ +"spawnflags" "768" +"classname" "trigger_once" +"target" "t101" +"model" "*30" +} +{ +"classname" "item_health" +"origin" "40 -16 464" +} +{ +"origin" "80 -48 464" +"classname" "item_health" +} +{ +"origin" "520 -72 296" +"classname" "item_shells" +} +{ +"spawnflags" "1" +"origin" "-424 -216 296" +"classname" "item_shells" +} +{ +"spawnflags" "769" +"angle" "270" +"origin" "880 -400 568" +"classname" "monster_wizard" +} +{ +"light" "200" +"origin" "432 176 152" +"classname" "light" +} +{ +"light" "150" +"origin" "432 -56 256" +"classname" "light" +} +{ +"origin" "264 -96 300" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "264 -140 300" +} +{ +"spawnflags" "1" +"origin" "1184 1568 240" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "1184 1616 240" +"spawnflags" "1" +} +{ +"light" "150" +"origin" "1496 1112 108" +"classname" "light" +} +{ +"light" "200" +"origin" "1120 1152 96" +"classname" "light" +} +{ +"light" "200" +"origin" "1080 692 184" +"classname" "light" +} +{ +"light" "300" +"classname" "light_flame_small_yellow" +"origin" "832 1184 294" +} +{ +"origin" "464 536 358" +"classname" "light_flame_small_yellow" +"light" "300" +} +{ +"light" "300" +"classname" "light_flame_small_yellow" +"origin" "600 704 334" +} +{ +"light" "150" +"origin" "1736 1096 110" +"classname" "light" +} +{ +"light" "100" +"origin" "832 1056 134" +"classname" "light" +} +{ +"light" "150" +"origin" "784 704 294" +"classname" "light" +} +{ +"origin" "856 592 182" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "824 552 182" +} +{ +"classname" "info_player_deathmatch" +"origin" "-416 -144 320" +"angle" "90" +} +{ +"classname" "info_player_deathmatch" +"origin" "168 -480 320" +"angle" "45" +} +{ +"classname" "info_player_deathmatch" +"origin" "1496 1328 200" +"angle" "270" +} +{ +"classname" "info_player_deathmatch" +"origin" "1936 -136 312" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "936 -1216 432" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "792 -992 440" +"angle" "45" +} +{ +"classname" "info_player_deathmatch" +"origin" "1080 -720 312" +"angle" "0" +} +{ +"classname" "info_player_deathmatch" +"origin" "408 -752 432" +"angle" "270" +} +{ +"classname" "info_player_deathmatch" +"origin" "792 -208 320" +"angle" "45" +} +{ +"classname" "info_player_deathmatch" +"origin" "784 808 206" +"angle" "225" +} +{ +"sounds" "3" +"wait" "3" +"angle" "90" +"classname" "func_door" +"model" "*31" +} +{ +"sounds" "0" +"wait" "3" +"angle" "270" +"classname" "func_door" +"model" "*32" +} +{ +"spawnflags" "1" +"origin" "680 832 182" +"classname" "item_shells" +} +{ +"origin" "1392 240 300" +"classname" "weapon_supershotgun" +} +{ +"spawnflags" "769" +"angle" "270" +"origin" "954 -754 444" +"classname" "monster_ogre" +} +{ +"spawnflags" "1" +"origin" "520 -1280 408" +"classname" "item_shells" +} +{ +"light" "200" +"origin" "-612 -500 548" +"classname" "light" +} +{ +"classname" "func_door" +"angle" "91" // svdijk -- changed to prevent z-fighting (was "90") +"targetname" "t110" +"wait" "-1" +"model" "*33" +} +{ +"sounds" "3" +"classname" "func_door" +"angle" "269" // svdijk -- changed to prevent z-fighting (was "270") +"wait" "-1" +"model" "*34" +} +{ +"classname" "trigger_once" +"target" "t110" +"model" "*35" +} +{ +"classname" "trigger_changelevel" +"map" "e1m3" +"model" "*36" +} +{ +"spawnflags" "1792" +"origin" "680 728 184" +"classname" "weapon_rocketlauncher" +} +{ +"spawnflags" "1792" +"origin" "1496 1256 176" +"classname" "weapon_nailgun" +} +{ +"angle" "180" +"spawnflags" "1792" +"origin" "-96 -496 360" +"classname" "weapon_supernailgun" +} +{ +"spawnflags" "1794" +"origin" "-112 -8 464" +"classname" "item_health" +} +{ +"spawnflags" "1793" +"origin" "-112 -568 360" +"classname" "item_spikes" +} +{ +"spawnflags" "1792" +"origin" "1616 1424 176" +"classname" "item_spikes" +} +{ +"spawnflags" "1792" +"classname" "item_spikes" +"origin" "1656 1424 176" +} +{ +"spawnflags" "1792" +"origin" "1696 1424 176" +"classname" "item_spikes" +} +{ +"spawnflags" "768" +"target" "t34" +"angle" "315" +"origin" "1070 646 312" +"classname" "monster_ogre" +} +{ +"spawnflags" "768" +"targetname" "t84" +"angle" "90" +"origin" "1624 88 376" +"classname" "monster_wizard" +} +{ +"spawnflags" "768" +"angle" "90" +"targetname" "t84" +"origin" "1866 -378 312" +"classname" "monster_ogre" +} +{ +"angle" "45" +"origin" "1088 -1096 440" +"classname" "monster_knight" +"targetname" "t50" +} +{ +"spawnflags" "768" +"classname" "monster_knight" +"origin" "1400 -1144 440" +"angle" "90" +"targetname" "t50" +} +{ +"spawnflags" "256" +"target" "t111" +"targetname" "t112" +"origin" "896 -1216 416" +"classname" "path_corner" +} +{ +"spawnflags" "256" +"target" "t112" +"targetname" "t111" +"classname" "path_corner" +"origin" "704 -1216 416" +} +{ +"spawnflags" "257" +"target" "t111" +"angle" "180" +"origin" "758 -1218 432" +"classname" "monster_army" +} +{ +"spawnflags" "768" +"target" "t114" +"targetname" "t113" +"origin" "-96 -520 368" +"classname" "path_corner" +} +{ +"spawnflags" "768" +"target" "t113" +"targetname" "t114" +"origin" "-96 -152 304" +"classname" "path_corner" +} +{ +"targetname" "t116" +"spawnflags" "769" +"target" "t113" +"angle" "270" +"origin" "-98 -194 320" +"classname" "monster_ogre" +} +{ +"spawnflags" "1536" +"origin" "1936 -96 289" +"classname" "item_health" +} +{ +"spawnflags" "1025" +"origin" "1040 -1200 417" +"classname" "item_health" +} +{ +"spawnflags" "769" +"target" "t117" +"angle" "315" +"origin" "-560 -312 592" +"classname" "monster_wizard" +} +{ +"spawnflags" "768" +"target" "t118" +"targetname" "t117" +"origin" "-528 -344 576" +"classname" "path_corner" +} +{ +"spawnflags" "768" +"target" "t117" +"targetname" "t118" +"origin" "-352 -656 576" +"classname" "path_corner" +} +{ +"classname" "light" +"origin" "1360 976 224" +"light" "150" +} +{ +"light" "150" +"origin" "1616 976 224" +"classname" "light" +} +{ +"classname" "light" +"origin" "1208 1296 368" +"light" "250" +} +{ +"origin" "1784 1288 368" +"classname" "light" +"light" "250" +} +{ +"classname" "light" +"origin" "1496 1664 336" +"light" "250" +} +{ +"classname" "light" +"origin" "1752 1176 112" +"light" "150" +} +{ +"light" "200" +"origin" "1776 976 112" +"classname" "light" +} +{ +"classname" "light" +"origin" "1216 976 112" +"light" "200" +} +{ +"light" "150" +"origin" "1224 1176 112" +"classname" "light" +} +{ +"classname" "light" +"origin" "1496 1432 520" +"light" "250" +} +{ +"classname" "light" +"origin" "1496 1304 264" +"light" "200" +} +{ +"classname" "light" +"origin" "1496 1432 288" +"light" "200" +} +{ +"classname" "light" +"origin" "1608 1120 88" +"light" "150" +} +{ +"light" "150" +"origin" "1384 1120 88" +"classname" "light" +} +{ +"classname" "light" +"origin" "1496 864 368" +"light" "150" +} +{ +"light" "175" +"origin" "980 764 353" +"classname" "light" +} +{ +"classname" "light" +"origin" "1228 764 353" +"light" "175" +} +{ +"classname" "light" +"origin" "1104 464 353" +"light" "200" +} +{ +"classname" "light" +"origin" "1104 -40 423" +"light" "200" +} +{ +"light" "150" +"origin" "1416 -128 367" +"classname" "light" +} +{ +"classname" "light" +"origin" "1104 -184 367" +"light" "200" +} +{ +"classname" "light" +"origin" "1184 56 423" +"light" "150" +} +{ +"light" "150" +"origin" "1024 56 423" +"classname" "light" +} +{ +"classname" "light" +"origin" "1272 -64 399" +"light" "150" +} +{ +"light" "150" +"origin" "888 -64 399" +"classname" "light" +} +{ +"classname" "light" +"origin" "1104 152 129" +"light" "300" +} +{ +"classname" "light" +"origin" "976 392 129" +"light" "200" +} +{ +"classname" "light" +"origin" "1104 656 120" +} +{ +"classname" "light" +"origin" "896 712 144" +"light" "200" +} +{ +"classname" "light" +"origin" "640 704 280" +"light" "200" +} +{ +"classname" "light" +"origin" "464 496 296" +"light" "150" +} +{ +"classname" "light" +"origin" "888 1152 96" +"light" "200" +} +{ +"classname" "light" +"origin" "840 880 240" +"light" "200" +} +{ +"classname" "light" +"origin" "848 584 240" +"light" "150" +} +{ +"classname" "light" +"origin" "784 160 144" +"light" "200" +} +{ +"classname" "light" +"origin" "440 336 144" +"light" "150" +} +{ +"light" "150" +"origin" "584 336 144" +"classname" "light" +} +{ +"classname" "light" +"origin" "432 24 136" +"light" "150" +} +{ +"classname" "light" +"origin" "656 328 224" +"light" "200" +} +{ +"classname" "light" +"origin" "432 -128 312" +"light" "200" +} +{ +"classname" "light" +"origin" "600 -384 360" +"light" "200" +} +{ +"origin" "520 -128 406" +"classname" "light_flame_small_yellow" +"light" "250" +} +{ +"classname" "light" +"origin" "424 -320 352" +"light" "200" +} +{ +"classname" "light" +"origin" "664 -1216 472" +"light" "150" +} +{ +"classname" "light" +"origin" "336 -1208 504" +"light" "150" +} +{ +"light" "150" +"origin" "336 -1008 504" +"classname" "light" +} +{ +"classname" "light" +"origin" "336 -816 504" +"light" "150" +} +{ +"classname" "light" +"origin" "880 -1000 496" +"light" "200" +} +{ +"classname" "light" +"origin" "880 -792 496" +"light" "200" +} +{ +"classname" "light" +"origin" "880 -376 304" +"light" "200" +} +{ +"classname" "light" +"origin" "1048 -912 480" +"light" "225" +} +{ +"classname" "light" +"origin" "1120 -1192 468" +"light" "150" +} +{ +"light" "150" +"origin" "1376 -1192 468" +"classname" "light" +} +{ +"classname" "light" +"origin" "1472 -912 464" +"light" "175" +} +{ +"classname" "light" +"origin" "880 -304 480" +"light" "100" +} +{ +"classname" "light" +"origin" "880 -680 480" +"light" "175" +} +{ +"classname" "light" +"origin" "1600 -704 484" +"light" "150" +} +{ +"classname" "light" +"origin" "1504 -704 348" +"light" "175" +} +{ +"light" "175" +"origin" "1336 -704 348" +"classname" "light" +} +{ +"classname" "light" +"origin" "1152 -640 332" +"light" "200" +} +{ +"classname" "light" +"origin" "1096 -552 348" +"light" "150" +} +{ +"light" "200" +"origin" "1160 -456 332" +"classname" "light" +} +{ +"light" "150" +"origin" "1216 -384 348" +"classname" "light" +} +{ +"classname" "light" +"origin" "1344 -384 348" +"light" "150" +} +{ +"classname" "light" +"origin" "1544 392 156" +"light" "225" +} +{ +"light" "225" +"origin" "1848 248 156" +"classname" "light" +} +{ +"classname" "light" +"origin" "1936 136 156" +"light" "225" +} +{ +"light" "200" +"origin" "2096 -80 156" +"classname" "light" +} +{ +"light" "200" +"origin" "2048 -408 156" +"classname" "light" +} +{ +"classname" "light" +"origin" "1456 -392 444" +"light" "225" +} +{ +"classname" "light" +"origin" "1640 -384 352" +"light" "225" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "2134 -474 316" +"light" "250" +} +{ +"light" "200" +"origin" "168 216 496" +"classname" "light" +} +{ +"classname" "light" +"origin" "-328 208 496" +"light" "200" +} +{ +"light" "200" +"origin" "-96 360 432" +"classname" "light" +} +{ +"classname" "light" +"origin" "-96 144 432" +"light" "200" +} +{ +"light" "200" +"origin" "-376 32 432" +"classname" "light" +} +{ +"light" "200" +"origin" "208 -72 432" +"classname" "light" +} +{ +"light" "150" +"origin" "-96 72 360" +"classname" "light" +} +{ +"light" "150" +"origin" "-64 -232 368" +"classname" "light" +} +{ +"light" "150" +"origin" "-96 -320 560" +"classname" "light" +} +{ +"light" "250" +"origin" "-96 -496 448" +"classname" "light" +} +{ +"light" "150" +"origin" "-416 -104 392" +"classname" "light" +} +{ +"light" "150" +"origin" "-344 -152 528" +"classname" "light" +} +{ +"classname" "light" +"origin" "160 -152 528" +"light" "150" +} +{ +"light" "150" +"origin" "-96 8 528" +"classname" "light" +} +{ +"light" "200" +"origin" "-560 -504 688" +"classname" "light" +} +{ +"classname" "light" +"origin" "-440 -368 688" +"light" "200" +} +{ +"light" "200" +"origin" "-440 -656 688" +"classname" "light" +} +{ +"classname" "light" +"origin" "-336 -504 688" +"light" "200" +} +{ +"classname" "light" +"origin" "2084 -208 336" +"light" "100" +} +{ +"light" "100" +"origin" "2012 -252 332" +"classname" "light" +} +{ +"classname" "light" +"origin" "1948 -328 332" +"light" "100" +} +{ +"light" "150" +"origin" "1892 -452 332" +"classname" "light" +} +{ +"sounds" "1" +"targetname" "t120" +"wait" "-1" +"angle" "-2" +"classname" "func_door" +"lip" "4" +"model" "*37" +} +{ +"target" "t120" +"classname" "trigger_once" +"model" "*38" +} +{ +"light" "100" +"origin" "2076 -312 336" +"classname" "light" +} +{ +"sounds" "3" +"spawnflags" "2064" +"angle" "0" +"wait" "-1" +"classname" "func_door" +"model" "*39" +} +{ +"spawnflags" "2064" +"wait" "-1" +"angle" "180" +"classname" "func_door" +"model" "*40" +} +{ +"light" "100" +"origin" "332 -264 356" +"classname" "light" +} +{ +"classname" "light" +"origin" "144 -264 356" +"light" "100" +} +{ +"light" "100" +"origin" "1104 572 316" +"classname" "light" +} +{ +"target" "t121" +"wait" ".8" +"classname" "trigger_multiple" +"model" "*41" +} +{ +"targetname" "t121" +"angle" "180" +"origin" "2120 -256 332" +"classname" "trap_spikeshooter" +"spawnflags" "1024" +} +{ +"targetname" "t121" +"angle" "90" +"origin" "1944 -456 332" +"classname" "trap_spikeshooter" +"spawnflags" "1024" +} +{ +"light" "150" +"origin" "1312 -856 472" +"classname" "light" +} +{ +"classname" "light" +"origin" "1184 -856 472" +"light" "175" +} +{ +"classname" "light" +"origin" "1560 -568 224" +"light" "200" +} +{ +"classname" "func_door" +"angle" "-2" +"wait" "-1" +"speed" "50" +"sounds" "1" +"targetname" "t123" +"lip" "6" +"model" "*42" +} +{ +"classname" "trigger_once" +"target" "t123" +"model" "*43" +} +{ +"classname" "light" +"origin" "1496 -552 330" +"light" "700" +"target" "t52" +} +{ +"classname" "light" +"origin" "1288 80 140" +"light" "250" +} +{ +"classname" "light" +"origin" "1288 400 80" +"light" "200" +} +{ +"classname" "light" +"origin" "1328 -664 160" +"light" "200" +} +{ +"classname" "item_armor1" +"origin" "784 56 304" +} +{ +"classname" "light" +"origin" "1544 464 352" +"light" "75" +} +{ +"classname" "func_plat" +"model" "*44" +} +{ +"classname" "light" +"origin" "1496 1192 280" +"light" "200" +} +{ +"classname" "light" +"origin" "1608 1192 136" +"light" "100" +} +{ +"light" "100" +"origin" "1384 1184 136" +"classname" "light" +} +{ +"light" "100" +"origin" "1608 1048 136" +"classname" "light" +} +{ +"classname" "light" +"origin" "1384 1048 136" +"light" "100" +} +{ +"classname" "light" +"origin" "1200 1148 92" +"light" "150" +} +{ +"light" "150" +"origin" "876 -184 367" +"classname" "light" +} +{ +"classname" "light" +"origin" "768 -128 384" +"light" "200" +} +{ +"classname" "light" +"origin" "1104 388 552" +"light" "250" +} +{ +"light" "200" +"origin" "1392 240 384" +"classname" "light" +} +{ +"classname" "light" +"origin" "1392 80 368" +"light" "200" +} +{ +"classname" "light" +"origin" "1272 400 367" +"light" "150" +} +{ +"light" "150" +"origin" "920 400 367" +"classname" "light" +} +{ +"classname" "light" +"origin" "816 208 368" +"light" "200" +} +{ +"classname" "light" +"origin" "800 24 368" +"light" "200" +} +{ +"classname" "light" +"origin" "800 376 385" +"light" "150" +} +{ +"light" "150" +"origin" "1400 400 385" +"classname" "light" +} +{ +"classname" "path_corner" +"origin" "1104 336 300" +"target" "t126" +"targetname" "t127" +} +{ +"origin" "1104 24 300" +"classname" "path_corner" +"targetname" "t126" +"target" "t127" +} +{ +"classname" "monster_army" +"origin" "1104 424 316" +"angle" "270" +"target" "t127" +} +{ +"targetname" "t128" +"origin" "1392 240 308" +"classname" "info_null" +} +{ +"light" "300" +"target" "t128" +"origin" "1392 240 376" +"classname" "light" +} +{ +"targetname" "t129" +"angle" "0" +"origin" "552 -128 320" +"classname" "monster_army" +} +{ +"target" "t129" +"classname" "trigger_once" +"model" "*45" +} +{ +"sounds" "1" +"classname" "trigger_secret" +"model" "*46" +} +{ +"sounds" "1" +"classname" "trigger_secret" +"model" "*47" +} +{ +"classname" "light" +"origin" "1104 24 536" +"light" "350" +} +{ +"spawnflags" "1" +"classname" "func_door_secret" +"angle" "270" +"model" "*48" +} +{ +"light" "200" +"origin" "1680 1552 320" +"classname" "light" +} +{ +"classname" "light" +"origin" "1312 1552 320" +"light" "200" +} +{ +"classname" "item_spikes" +"origin" "1480 1104 68" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "1760 -568 256" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "1232 -1200 416" +} +{ +"message" "This door is opened elsewhere..." +"classname" "func_door" +"sounds" "3" +"angle" "180" +"wait" "-1" +"targetname" "t122" +"speed" "35" +"spawnflags" "2048" +"model" "*49" +} +{ +"classname" "func_door" +"angle" "0" +"wait" "-1" +"speed" "30" +"spawnflags" "2048" +"model" "*50" +} +{ +"classname" "light" +"origin" "1496 1600 296" +"light" "150 " +} +{ +"light" "150" +"origin" "1568 1664 296" +"classname" "light" +} +{ +"classname" "light" +"origin" "1424 1664 296" +"light" "150" +} +{ +"classname" "light" +"origin" "1328 1424 296" +"light" "200" +} +{ +"light" "250" +"origin" "1696 1416 296" +"classname" "light" +} +{ +"classname" "monster_army" +"origin" "1592 1296 200" +"angle" "270" +} +{ +"spawnflags" "768" +"classname" "monster_demon1" +"origin" "-96 576 320" +"angle" "270" +"targetname" "t73" +"target" "t143" +} +{ +"classname" "path_corner" +"origin" "1392 416 304" +"targetname" "t131" +"target" "t130" +"spawnflags" "768" +} +{ +"origin" "1392 296 304" +"classname" "path_corner" +"targetname" "t130" +"target" "t131" +"spawnflags" "768" +} +{ +"classname" "monster_army" +"origin" "1392 352 320" +"angle" "270" +"target" "t130" +"spawnflags" "768" +} +{ +"target" "t132" +"targetname" "t133" +"origin" "296 -328 304" +"classname" "path_corner" +} +{ +"target" "t133" +"targetname" "t132" +"classname" "path_corner" +"origin" "472 -416 304" +} +{ +"spawnflags" "1" +"target" "t132" +"angle" "90" +"origin" "472 -456 320" +"classname" "monster_army" +} +{ +"spawnflags" "1" +"targetname" "t89" +"angle" "135" +"origin" "1712 -784 376" +"classname" "monster_army" +} +{ +"target" "t135" +"spawnflags" "256" +"targetname" "t134" +"origin" "400 -1128 416" +"classname" "path_corner" +} +{ +"target" "t134" +"spawnflags" "256" +"targetname" "t135" +"classname" "path_corner" +"origin" "400 -1248 416" +} +{ +"target" "t134" +"spawnflags" "257" +"angle" "90" +"origin" "408 -1208 432" +"classname" "monster_army" +} +{ +"targetname" "t101" +"angle" "90" +"origin" "-288 -24 488" +"classname" "monster_army" +"spawnflags" "768" +} +{ +"spawnflags" "768" +"targetname" "t101" +"classname" "monster_army" +"origin" "136 -128 488" +"angle" "90" +} +{ +"spawnflags" "1792" +"origin" "-264 -24 464" +"classname" "item_rockets" +} +{ +"spawnflags" "2048" +"origin" "-240 -8 464" +"classname" "item_spikes" +} +{ +"classname" "monster_ogre" +"origin" "-304 -304 488" +"angle" "225" +"spawnflags" "769" +} +{ +"classname" "func_wall" +"spawnflags" "768" +"model" "*51" +} +{ +"classname" "trap_spikeshooter" +"origin" "2048 -48 332" +"angle" "270" +"spawnflags" "769" +"targetname" "t121" +} +{ +"origin" "2048 -476 332" +"classname" "info_null" +"targetname" "t136" +} +{ +"style" "32" +"origin" "2048 -456 336" +"classname" "light" +"light" "800" +"spawnflags" "1" +"target" "t136" +"targetname" "t137" +} +{ +"style" "32" +"classname" "trigger_once" +"spawnflags" "768" +"target" "t137" +"model" "*52" +} +{ +"classname" "monster_wizard" +"origin" "672 328 384" +"angle" "180" +"spawnflags" "768" +"targetname" "t138" +} +{ +"classname" "trigger_once" +"target" "t138" +"model" "*53" +} +{ +"style" "32" +"classname" "light" +"origin" "2004 -52 332" +"light" "100" +"spawnflags" "1" +"targetname" "t137" +} +{ +"classname" "item_shells" +"origin" "1416 224 300" +"spawnflags" "768" +} +{ +"classname" "path_corner" +"origin" "-344 136 304" +"targetname" "t139" +"target" "t140" +"spawnflags" "768" +} +{ +"origin" "168 128 304" +"classname" "path_corner" +"target" "t139" +"targetname" "t140" +"spawnflags" "768" +} +{ +"classname" "monster_ogre" +"origin" "-400 168 320" +"spawnflags" "768" +"target" "t139" +} +{ +"classname" "trap_spikeshooter" +"origin" "2120 -256 332" +"angle" "180" +"spawnflags" "769" +"targetname" "t121" +} +{ +"classname" "trap_spikeshooter" +"origin" "1944 -456 332" +"targetname" "t121" +"angle" "90" +"spawnflags" "769" +} +{ +"classname" "item_spikes" +"origin" "-336 -80 470" +"spawnflags" "768" +} +{ +"targetname" "t143" +"classname" "trigger_teleport" +"target" "t142" +"spawnflags" "2" +"model" "*54" +} +{ +"targetname" "t143" +"classname" "trigger_teleport" +"target" "t141" +"spawnflags" "2" +"model" "*55" +} +{ +"classname" "monster_demon1" +"origin" "32 840 359" +"angle" "270" +"targetname" "t143" +"spawnflags" "768" +} +{ +"angle" "270" +"origin" "-192 840 359" +"classname" "monster_demon1" +"targetname" "t143" +"spawnflags" "768" +} +{ +"classname" "info_teleport_destination" +"origin" "80 216 303" +"angle" "270" +"targetname" "t141" +} +{ +"angle" "270" +"origin" "-264 224 303" +"classname" "info_teleport_destination" +"targetname" "t142" +} +{ +"wait" "-1" +"target" "t53" +"health" "1" +"classname" "func_button" +"model" "*56" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "1408 1296 200" +"classname" "monster_army" +} +{ +"classname" "item_shells" +"origin" "772 -856 420" +"spawnflags" "768" +} +{ +"spawnflags" "1792" +"origin" "1248 -1128 420" +"classname" "weapon_grenadelauncher" +} +{ +"spawnflags" "1793" +"origin" "864 -312 440" +"classname" "item_rockets" +} +{ +"classname" "trigger_once" +"message" "Pass through the arch to exit..." +"model" "*57" +} +{ +"mangle" "20 300 0" +"classname" "info_intermission" +"origin" "-224 424 512" +} +{ +"mangle" "20 45 0" +"origin" "1048 -744 488" +"classname" "info_intermission" +} +{ +"mangle" "20 270 0" +"origin" "1104 424 528" +"classname" "info_intermission" +} +{ +"mangle" "20 45 0" +"origin" "1240 984 416" +"classname" "info_intermission" +} +{ +"sounds" "1" +"speed" "20" +"classname" "func_button" +"angle" "0" +"wait" "-1" +"target" "t144" +"model" "*58" +} +{ +"classname" "light" +"origin" "400 -1392 480" +"light" "150" +} +{ +"classname" "func_door" +"angle" "-2" +"wait" "-1" +"speed" "20" +"sounds" "1" +"targetname" "t144" +"model" "*59" +} +{ +"classname" "trigger_secret" +"model" "*60" +} +{ +"classname" "item_artifact_super_damage" +"origin" "400 -1360 432" +} +{ +"classname" "item_spikes" +"origin" "808 -632 192" +"spawnflags" "2049" +} +{ +"classname" "item_health" +"origin" "924 -632 192" +"spawnflags" "2048" +} +{ +"classname" "weapon_supernailgun" +"origin" "880 -616 192" +"spawnflags" "1792" +} +{ +"classname" "ambient_drip" +"origin" "842 978 344" +} +{ +"classname" "ambient_drip" +"origin" "546 330 400" +} +{ +"classname" "info_player_coop" +"origin" "1608 1664 264" +"angle" "270" +} +{ +"angle" "270" +"origin" "1392 1664 264" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "1496 1560 264" +"angle" "270" +} +{ +"spawnflags" "256" +"angle" "270" +"origin" "232 -176 320" +"classname" "monster_ogre" +} +{ +"spawnflags" "1280" +"angle" "225" +"origin" "-368 -312 480" +"classname" "monster_knight" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*61" +} +{ +"origin" "1810 274 200" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "1802 -102 200" +} +{ +"origin" "2050 -214 200" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "2002 -390 200" +} +{ +"origin" "1738 -398 200" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "1346 -398 200" +} +{ +"origin" "1138 -542 200" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "882 -494 200" +} +{ +"classname" "ambient_swamp1" +"origin" "1722 1090 176" +} +{ +"origin" "1242 1090 176" +"classname" "ambient_swamp1" +} +{ +"classname" "ambient_swamp2" +"origin" "1106 642 192" +} +{ +"origin" "1346 242 192" +"classname" "ambient_swamp2" +} +{ +"classname" "ambient_swamp1" +"origin" "866 210 192" +} +{ +"classname" "ambient_swamp1" +"origin" "1802 90 192" +} +{ +"origin" "1546 -398 192" +"classname" "ambient_swamp1" +} +{ +"classname" "ambient_swamp2" +"origin" "2042 -310 192" +} +{ +"origin" "1178 -398 192" +"classname" "ambient_swamp2" +} +{ +"classname" "ambient_swamp2" +"origin" "1202 -678 192" +} diff --git a/Misc/qs_pak/maps/e1m4.diff b/Misc/qs_pak/maps/e1m4.diff new file mode 100644 index 0000000..7e420ff --- /dev/null +++ b/Misc/qs_pak/maps/e1m4.diff @@ -0,0 +1,30 @@ +--- e1m4.ent ++++ e1m4@958e.ent +@@ -2143,12 +2143,13 @@ + { + "classname" "func_door_secret" + "angle" "0" + "spawnflags" "1" + "targetname" "t91" + "model" "*53" ++"t_length" "73" // svdijk -- added to prevent z-fighting + } + { + "classname" "func_button" + "angle" "-2" + "wait" "-1" + "target" "t92" +@@ -2202,12 +2203,13 @@ + { + "spawnflags" "3" + "angle" "0" + "classname" "func_door_secret" + "targetname" "t91" + "model" "*60" ++"t_length" "73" // svdijk -- added to prevent z-fighting + } + { + "light" "150" + "origin" "680 -256 1144" + "classname" "light" + } diff --git a/Misc/qs_pak/maps/e1m4@958e.ent b/Misc/qs_pak/maps/e1m4@958e.ent new file mode 100644 index 0000000..77d4761 --- /dev/null +++ b/Misc/qs_pak/maps/e1m4@958e.ent @@ -0,0 +1,3171 @@ +{ +"message" "the Grisly Grotto" +"worldtype" "0" +"classname" "worldspawn" +"wad" "gfx/wizard.wad" +"sounds" "5" +} +{ +"classname" "light" +"origin" "464 480 1656" +"light" "300" +} +{ +"light" "400" +"origin" "712 296 1512" +"classname" "light" +} +{ +"sounds" "3" +"angle" "180" +"classname" "func_door" +"model" "*1" +} +{ +"angle" "0" +"classname" "func_door" +"model" "*2" +} +{ +"classname" "light_flame_small_yellow" +"origin" "560 -112 1374" +} +{ +"origin" "848 -112 1374" +"classname" "light_flame_small_yellow" +} +{ +"origin" "760 656 1536" +"classname" "light" +} +{ +"light" "400" +"origin" "834 498 1040" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "944 728 1456" +} +{ +"classname" "light_flame_small_yellow" +"origin" "1016 80 998" +} +{ +"origin" "392 80 998" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light" +"origin" "680 1224 516" +"light" "200" +} +{ +"light" "200" +"origin" "704 992 516" +"classname" "light" +} +{ +"light" "200" +"origin" "696 1608 628" +"classname" "light" +} +{ +"origin" "704 1368 588" +"classname" "light" +} +{ +"origin" "816 1616 444" +"classname" "light" +} +{ +"classname" "light" +"origin" "712 1728 444" +} +{ +"origin" "592 1608 444" +"classname" "light" +} +{ +"classname" "light" +"origin" "624 1096 444" +} +{ +"light" "300" +"origin" "432 1328 816" +"classname" "light" +} +{ +"classname" "light" +"origin" "696 1112 816" +"light" "300" +} +{ +"classname" "light" +"origin" "704 -80 960" +"light" "200" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "818 18 948" +"light" "200" +} +{ +"origin" "586 18 948" +"classname" "light_torch_small_walltorch" +"light" "200" +} +{ +"light" "200" +"origin" "688 496 880" +"classname" "light" +} +{ +"origin" "489 483 1356" +"classname" "light_flame_large_yellow" +} +{ +"light" "200" +"origin" "704 -120 1360" +"classname" "light" +} +{ +"classname" "light" +"origin" "1216 936 1560" +"light" "200" +} +{ +"origin" "1294 826 1576" +"classname" "light_flame_large_yellow" +} +{ +"sounds" "1" +"targetname" "t1" +"wait" "-1" +"angle" "180" +"classname" "func_door" +"model" "*3" +} +{ +"angle" "0" +"wait" "-1" +"classname" "func_door" +"model" "*4" +} +{ +"target" "t1" +"classname" "trigger_once" +"model" "*5" +} +{ +"map" "e1m5" +"classname" "trigger_changelevel" +"model" "*6" +} +{ +"wait" "-1" +"angle" "0" +"classname" "func_door" +"speed" "50" +"model" "*7" +} +{ +"classname" "info_player_start" +"origin" "-256 2272 1240" +"angle" "270" +} +{ +"targetname" "t23" +"classname" "func_door" +"angle" "180" +"wait" "-1" +"speed" "50" +"sounds" "3" +"model" "*8" +} +{ +"classname" "light" +"origin" "696 704 820" +} +{ +"classname" "light" +"origin" "704 776 672" +"light" "200" +} +{ +"classname" "light" +"origin" "360 904 520" +"light" "200" +} +{ +"origin" "704 856 400" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "944 880 416" +"light" "150" +} +{ +"origin" "1056 1176 424" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "1096 1408 360" +} +{ +"classname" "light" +"origin" "416 1696 360" +} +{ +"origin" "328 1368 360" +"classname" "light" +"light" "200" +} +{ +"light" "200 " +"classname" "light" +"origin" "696 752 896" +} +{ +"light" "250" +"origin" "798 1850 1024" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "642 1850 1024" +"light" "250" +} +{ +"light" "200 " +"origin" "700 1364 952" +"classname" "light" +} +{ +"classname" "light_flame_large_yellow" +"origin" "1094 1494 1064" +} +{ +"origin" "324 1104 1064" +"classname" "light_flame_large_yellow" +} +{ +"light" "200 " +"origin" "704 1660 952" +"classname" "light" +} +{ +"sounds" "3" +"classname" "func_door" +"angle" "180" +"wait" "-1" +"targetname" "t2" +"model" "*9" +} +{ +"sounds" "3" +"classname" "func_door" +"wait" "-1" +"angle" "0" +"targetname" "t4" +"model" "*10" +} +{ +"classname" "trigger_once" +"targetname" "t4" +"target" "t7" +"model" "*11" +} +{ +"classname" "trigger_once" +"targetname" "t2" +"target" "t7" +"model" "*12" +} +{ +"dmg" "90" +"speed" "200" +"classname" "func_train" +"target" "t5" +"targetname" "t8" +"model" "*13" +} +{ +"classname" "path_corner" +"origin" "-359 1528 1316" +"targetname" "t5" +"target" "t6" +} +{ +"classname" "path_corner" +"origin" "-359 1528 880" +"targetname" "t6" +"target" "t5" +"wait" "-1" +} +{ +"classname" "trigger_counter" +"targetname" "t7" +"target" "t8" +"count" "2" +"model" "*14" +} +{ +"targetname" "t11" +"origin" "-96 1640 1256" +"classname" "info_null" +} +{ +"targetname" "t9" +"origin" "-416 1640 1256" +"classname" "info_null" +} +{ +"light" "400" +"target" "t9" +"origin" "-396 1640 1256" +"classname" "light" +} +{ +"light" "400" +"target" "t11" +"origin" "-116 1640 1256" +"classname" "light" +} +{ +"classname" "light" +"origin" "-328 1564 1532" +} +{ +"classname" "light_flame_small_yellow" +"origin" "-88 1640 1514" +} +{ +"origin" "-424 1640 1514" +"classname" "light_flame_small_yellow" +} +{ +"origin" "-248 1464 1154" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light_flame_small_yellow" +"origin" "-256 1808 1046" +} +{ +"light" "200" +"origin" "-164 1732 1268" +"classname" "light" +} +{ +"classname" "light" +"origin" "-348 1732 1268" +"light" "200" +} +{ +"light" "150" +"origin" "-248 1500 1056" +"classname" "light" +} +{ +"light" "150" +"origin" "-256 1772 956" +"classname" "light" +} +{ +"classname" "light" +"origin" "-128 1636 920" +"light" "150" +} +{ +"light" "150" +"origin" "-124 1640 920" +"classname" "light" +} +{ +"classname" "light" +"origin" "-172 1524 920" +"light" "150" +} +{ +"light" "150" +"origin" "-284 1516 920" +"classname" "light" +} +{ +"classname" "light" +"origin" "-360 1580 920" +"light" "150" +} +{ +"light" "75" +"origin" "-360 1700 920" +"classname" "light" +} +{ +"classname" "light" +"origin" "72 1632 928" +"light" "125" +} +{ +"light" "150" +"origin" "80 1488 928" +"classname" "light" +} +{ +"classname" "light_flame_large_yellow" +"origin" "158 1308 1064" +} +{ +"classname" "light" +"origin" "-192 1688 1380" +"light" "100" +} +{ +"light" "100" +"origin" "-192 1592 1380" +"classname" "light" +} +{ +"classname" "light" +"origin" "-320 1592 1380" +"light" "100" +} +{ +"light" "100" +"origin" "-320 1688 1380" +"classname" "light" +} +{ +"classname" "light" +"origin" "-384 1640 1452" +"light" "125" +} +{ +"classname" "light" +"origin" "-112 1640 1452" +"light" "125" +} +{ +"light" "200" +"origin" "-248 1504 1336" +"classname" "light" +} +{ +"classname" "light" +"origin" "-128 1640 1336" +"light" "200" +} +{ +"light" "200" +"origin" "-384 1640 1336" +"classname" "light" +} +{ +"classname" "light" +"origin" "696 1608 816" +"light" "300" +} +{ +"light" "200" +"classname" "light" +"origin" "888 1328 424" +} +{ +"classname" "light" +"origin" "160 1352 960" +"light" "150" +} +{ +"light" "150" +"origin" "368 1104 1000" +"classname" "light" +} +{ +"classname" "light" +"origin" "1048 1504 1000" +"light" "150" +} +{ +"classname" "light" +"origin" "992 992 1048" +"light" "150" +} +{ +"classname" "light" +"origin" "48 1384 1008" +"light" "100" +} +{ +"light" "150" +"origin" "1080 1144 1048" +"classname" "light" +} +{ +"classname" "light" +"origin" "968 824 976" +"light" "150" +} +{ +"classname" "light" +"origin" "896 1328 1000" +"light" "150" +} +{ +"classname" "light" +"origin" "368 1616 1000" +"light" "175" +} +{ +"classname" "light" +"origin" "256 1496 824" +"light" "150" +} +{ +"light" "150" +"origin" "256 1352 824" +"classname" "light" +} +{ +"classname" "light" +"origin" "856 1368 536" +"light" "200" +} +{ +"light" "150" +"origin" "552 1360 536" +"classname" "light" +} +{ +"classname" "light" +"origin" "872 1552 1056" +"light" "150" +} +{ +"classname" "light" +"origin" "1032 1056 904" +"light" "150" +} +{ +"light" "150" +"origin" "1080 1184 904" +"classname" "light" +} +{ +"classname" "light" +"origin" "864 848 904" +"light" "150" +} +{ +"classname" "light" +"origin" "312 1496 1016" +"light" "175" +} +{ +"light" "200" +"origin" "704 1368 808" +"classname" "light" +} +{ +"light" "150" +"origin" "464 816 904" +"classname" "light" +} +{ +"light" "200" +"origin" "944 888 800" +"classname" "light" +} +{ +"light" "150" +"origin" "888 1112 728" +"classname" "light" +} +{ +"light" "175" +"origin" "1008 1504 728" +"classname" "light" +} +{ +"light" "200" +"origin" "720 1848 1200" +"classname" "light" +} +{ +"light" "200" +"origin" "704 888 672" +"classname" "light" +} +{ +"light" "175" +"origin" "512 1456 1040" +"classname" "light" +} +{ +"light" "150" +"origin" "440 840 380" +"classname" "light" +} +{ +"light" "150" +"origin" "720 1848 1028" +"classname" "light" +} +{ +"light" "150" +"origin" "720 1936 1024" +"classname" "light" +} +{ +"origin" "544 2128 984" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"origin" "712 1912 576" +"classname" "light" +} +{ +"light" "300" +"origin" "720 2544 856" +"classname" "light" +} +{ +"light" "200" +"origin" "888 2048 592" +"classname" "light" +} +{ +"origin" "704 2496 1128" +"classname" "light" +} +{ +"origin" "512 2048 976" +"classname" "light" +"light" "150" +} +{ +"origin" "952 2328 528" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "700 2760 808" +"light" "200" +} +{ +"classname" "light" +"origin" "700 2800 616" +"light" "200" +} +{ +"classname" "light" +"origin" "584 2780 584" +"light" "175" +} +{ +"light" "175" +"origin" "808 2780 584" +"classname" "light" +} +{ +"sounds" "3" +"wait" "-1" +"targetname" "t29" +"classname" "func_door" +"angle" "270" +"model" "*15" +} +{ +"message" "This door is opened elsewhere..." +"wait" "-1" +"classname" "func_door" +"angle" "90" +"model" "*16" +} +{ +"classname" "light" +"origin" "424 2304 1000" +"light" "200" +} +{ +"light" "300" +"origin" "696 2880 1062" +"classname" "light_flame_small_yellow" +} +{ +"light" "200" +"classname" "light" +"origin" "864 2128 984" +} +{ +"classname" "light_flame_large_yellow" +"origin" "702 2154 1228" +"light" "0" +} +{ +"classname" "light" +"origin" "832 2008 1136" +"light" "200" +} +{ +"classname" "light" +"origin" "584 2008 1136" +"light" "200" +} +{ +"light" "200" +"origin" "272 2016 1136" +"classname" "light" +} +{ +"classname" "light" +"origin" "272 2320 1136" +"light" "150" +} +{ +"light" "150" +"origin" "1104 2408 984" +"classname" "light" +} +{ +"sounds" "2" +"classname" "func_plat" +"wait" "4" +"model" "*17" +} +{ +"classname" "light" +"origin" "700 2844 996" +"light" "200" +} +{ +"classname" "light" +"origin" "704 2216 1252" +} +{ +"origin" "936 2304 1252" +"classname" "light" +} +{ +"classname" "light" +"origin" "936 2536 1252" +} +{ +"light" "300" +"classname" "light" +"origin" "488 2536 1252" +} +{ +"origin" "488 2304 1252" +"classname" "light" +"light" "300" +} +{ +"light" "0" +"origin" "998 2306 1228" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light_flame_large_yellow" +"origin" "998 2534 1228" +"light" "0" +} +{ +"light" "0" +"origin" "426 2534 1228" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light_flame_large_yellow" +"origin" "426 2306 1228" +"light" "0" +} +{ +"classname" "light" +"origin" "704 2152 984" +"light" "200" +} +{ +"classname" "light_flame_small_yellow" +"origin" "1160 2400 1038" +"light" "250" +} +{ +"origin" "840 2536 630" +"classname" "light_flame_small_yellow" +"light" "250" +} +{ +"classname" "light_flame_small_yellow" +"origin" "584 2544 630" +"light" "250" +} +{ +"light" "250" +"origin" "408 2304 694" +"classname" "light_flame_small_yellow" +} +{ +"light" "200" +"classname" "light" +"origin" "960 2632 528" +} +{ +"classname" "light" +"origin" "960 2480 528" +"light" "150" +} +{ +"classname" "light" +"origin" "456 2304 624" +"light" "175" +} +{ +"classname" "light" +"origin" "704 2416 576" +"light" "250" +} +{ +"light" "150" +"origin" "728 2184 528" +"classname" "light" +} +{ +"classname" "light" +"origin" "512 2176 528" +"light" "150" +} +{ +"classname" "light" +"origin" "832 2336 656" +"light" "175" +} +{ +"light" "175" +"origin" "592 2336 656" +"classname" "light" +} +{ +"classname" "light" +"origin" "808 2584 576" +"light" "150" +} +{ +"light" "150" +"origin" "616 2576 576" +"classname" "light" +} +{ +"classname" "light_flame_small_yellow" +"origin" "488 2712 694" +"light" "250" +} +{ +"classname" "light" +"origin" "488 2624 608" +"light" "175" +} +{ +"classname" "light" +"origin" "480 2464 608" +"light" "175" +} +{ +"light" "250" +"origin" "184 2184 1038" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light_flame_small_yellow" +"origin" "512 2096 1038" +"light" "250" +} +{ +"classname" "light" +"origin" "224 2184 976" +"light" "150" +} +{ +"classname" "light" +"origin" "848 2024 992" +"light" "200" +} +{ +"classname" "light" +"origin" "256 1992 1000" +"light" "150" +} +{ +"classname" "light" +"origin" "272 2376 1000" +"light" "200" +} +{ +"classname" "light" +"origin" "1112 2208 1032" +"light" "150" +} +{ +"light" "0" +"origin" "698 2860 1228" +"classname" "light_flame_large_yellow" +} +{ +"origin" "700 2808 1252" +"classname" "light" +} +{ +"light" "250" +"origin" "472 2632 1062" +"classname" "light" +} +{ +"classname" "light" +"origin" "952 2632 1062" +"light" "250" +} +{ +"light" "150" +"origin" "952 2424 894" +"classname" "light" +} +{ +"classname" "light" +"origin" "480 2424 894" +"light" "150" +} +{ +"light" "150" +"origin" "896 2296 966" +"classname" "light" +} +{ +"classname" "light" +"origin" "704 2304 966" +"light" "150" +} +{ +"light" "150" +"origin" "552 2304 966" +"classname" "light" +} +{ +"light" "250" +"origin" "704 2184 406" +"classname" "light" +} +{ +"light" "150" +"origin" "712 2000 406" +"classname" "light" +} +{ +"light" "200" +"origin" "504 2224 400" +"classname" "light" +} +{ +"light" "200" +"origin" "960 2576 400" +"classname" "light" +} +{ +"light" "150" +"origin" "960 2400 400" +"classname" "light" +} +{ +"light" "150" +"origin" "808 1112 592" +"classname" "light" +} +{ +"classname" "light" +"origin" "600 1112 592" +"light" "150" +} +{ +"light" "175" +"origin" "1016 128 936" +"classname" "light" +} +{ +"classname" "light" +"origin" "392 128 936" +"light" "175" +} +{ +"light" "150" +"origin" "848 -64 1304" +"classname" "light" +} +{ +"classname" "light" +"origin" "560 -64 1304" +"light" "150" +} +{ +"light" "175" +"origin" "1248 832 1496" +"classname" "light" +} +{ +"light" "150" +"origin" "1096 872 1496" +"classname" "light" +} +{ +"light" "150" +"origin" "896 2208 976" +"classname" "light" +} +{ +"classname" "light" +"origin" "1096 200 1512" +"light" "350" +} +{ +"light" "350" +"origin" "264 192 1512" +"classname" "light" +} +{ +"light" "175" +"origin" "488 448 1264" +"classname" "light" +} +{ +"origin" "1048 728 1456" +"classname" "light" +"light" "200" +} +{ +"light" "125" +"origin" "1328 928 1448" +"classname" "light" +} +{ +"light" "225" +"origin" "696 672 1320" +"classname" "light" +} +{ +"origin" "816 -56 1640" +"classname" "light" +} +{ +"classname" "light" +"origin" "584 -56 1640" +} +{ +"light" "175" +"origin" "880 96 1376" +"classname" "light" +} +{ +"classname" "light" +"origin" "528 96 1376" +"light" "175" +} +{ +"light" "200" +"origin" "960 344 1072" +"classname" "light" +} +{ +"classname" "light" +"origin" "1120 264 1072" +"light" "200" +} +{ +"light" "175" +"origin" "1208 96 1016" +"classname" "light" +} +{ +"light" "175" +"origin" "1024 360 968" +"classname" "light" +} +{ +"classname" "light" +"origin" "328 336 968" +"light" "150" +} +{ +"classname" "light" +"origin" "416 424 1072" +"light" "200" +} +{ +"light" "200" +"origin" "296 256 1072" +"classname" "light" +} +{ +"light" "150" +"origin" "704 48 1040" +"classname" "light" +} +{ +"light" "175" +"origin" "384 464 976" +"classname" "light" +} +{ +"classname" "light" +"origin" "224 264 976" +"light" "150" +} +{ +"light" "150" +"origin" "952 2192 416" +"classname" "light" +} +{ +"classname" "light" +"origin" "1040 -464 1016" +"light" "150" +} +{ +"sounds" "2" +"spawnflags" "1" +"classname" "func_plat" +"model" "*18" +} +{ +"light" "250" +"origin" "1072 -272 1262" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light" +"origin" "1024 -272 1200" +"light" "150" +} +{ +"light" "200" +"origin" "624 -240 1328" +"classname" "light" +} +{ +"light" "175" +"origin" "1080 -624 1168" +"classname" "light" +} +{ +"origin" "456 -576 1230" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light" +"origin" "504 -576 1168" +"light" "175" +} +{ +"origin" "624 -448 924" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light_flame_large_yellow" +"origin" "624 -704 924" +} +{ +"light" "125" +"origin" "664 -448 880" +"classname" "light" +} +{ +"classname" "light" +"origin" "624 -488 880" +"light" "125" +} +{ +"light" "125" +"origin" "584 -448 880" +"classname" "light" +} +{ +"classname" "light" +"origin" "624 -408 880" +"light" "125" +} +{ +"light" "125" +"origin" "624 -664 880" +"classname" "light" +} +{ +"classname" "light" +"origin" "664 -704 880" +"light" "125" +} +{ +"light" "125" +"origin" "624 -744 880" +"classname" "light" +} +{ +"classname" "light" +"origin" "584 -704 880" +"light" "125" +} +{ +"light" "250" +"origin" "296 -96 968" +"classname" "light" +} +{ +"light" "250" +"origin" "1112 -96 968" +"classname" "light" +} +{ +"light" "175" +"origin" "1056 -224 968" +"classname" "light" +} +{ +"light" "500" +"origin" "-264 2120 1504" +"classname" "light" +} +{ +"spawnflags" "2064" +"angle" "0" +"classname" "func_door" +"wait" "-1" +"model" "*19" +} +{ +"sounds" "3" +"classname" "func_door" +"angle" "180" +"spawnflags" "2064" +"wait" "-1" +"model" "*20" +} +{ +"light" "200" +"origin" "704 32 952" +"classname" "light" +} +{ +"target" "t101" +"spawnflags" "256" +"targetname" "t23" +"angle" "90" +"origin" "-248 1560 1224" +"classname" "monster_wizard" +} +{ +"target" "t23" +"classname" "trigger_once" +"model" "*21" +} +{ +"origin" "-280 1560 1348" +"classname" "item_armor2" +} +{ +"origin" "-488 2112 1220" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "-488 2064 1220" +} +{ +"spawnflags" "1536" +"origin" "-272 1784 1156" +"classname" "item_shells" +} +{ +"spawnflags" "256" +"target" "t25" +"targetname" "t24" +"origin" "888 1640 1028" +"classname" "path_corner" +} +{ +"spawnflags" "256" +"target" "t24" +"targetname" "t25" +"classname" "path_corner" +"origin" "624 1264 1028" +} +{ +"spawnflags" "256" +"target" "t24" +"origin" "928 1672 1028" +"classname" "monster_wizard" +} +{ +"spawnflags" "2304" +"target" "t26" +"classname" "trigger_once" +"model" "*22" +} +{ +"target" "t27" +"targetname" "t28" +"origin" "568 2040 928" +"classname" "path_corner" +} +{ +"target" "t28" +"targetname" "t27" +"classname" "path_corner" +"origin" "368 2044 928" +} +{ +"target" "t27" +"origin" "472 2040 944" +"classname" "monster_ogre" +"spawnflags" "1" +} +{ +"sounds" "2" +"target" "t29" +"wait" "-1" +"angle" "90" +"classname" "func_button" +"model" "*23" +} +{ +"sounds" "3" +"angle" "180" +"classname" "func_door" +"model" "*24" +} +{ +"angle" "0" +"classname" "func_door" +"model" "*25" +} +{ +"light" "175" +"origin" "1112 2520 964" +"classname" "light" +} +{ +"origin" "104 1308 892" +"classname" "item_health" +} +{ +"spawnflags" "2048" +"origin" "704 1344 936" +"classname" "item_key1" +"sounds" "1" +} +{ +"target" "t34" +"angle" "180" +"origin" "920 2040 544" +"classname" "monster_ogre" +"spawnflags" "1" +} +{ +"target" "t35" +"targetname" "t34" +"origin" "864 2044 528" +"classname" "path_corner" +} +{ +"target" "t37" +"targetname" "t35" +"classname" "path_corner" +"origin" "704 2048 528" +} +{ +"target" "t34" +"targetname" "t36" +"origin" "704 2048 528" +"classname" "path_corner" +} +{ +"target" "t36" +"targetname" "t37" +"classname" "path_corner" +"origin" "704 1808 528" +} +{ +"targetname" "t38" +"angle" "270" +"origin" "456 2476 544" +"classname" "monster_ogre" +} +{ +"target" "t38" +"classname" "trigger_once" +"model" "*26" +} +{ +"targetname" "t29" +"angle" "0" +"origin" "336 2272 952" +"classname" "monster_knight" +"spawnflags" "768" +} +{ +"targetname" "t39" +"spawnflags" "1" +"wait" "-1" +"angle" "-2" +"classname" "func_door" +"sounds" "1" +"model" "*27" +} +{ +"targetname" "t39" +"angle" "270" +"origin" "712 2540 448" +"classname" "monster_ogre" +} +{ +"target" "t39" +"classname" "trigger_once" +"model" "*28" +} +{ +"classname" "light" +"origin" "712 2544 440" +"light" "200" +} +{ +"classname" "item_health" +"origin" "1064 2184 920" +"spawnflags" "1024" +} +{ +"spawnflags" "1" +"origin" "1128 2184 920" +"classname" "item_health" +} +{ +"classname" "item_spikes" +"origin" "816 2840 920" +} +{ +"origin" "816 2800 920" +"classname" "item_spikes" +} +{ +"spawnflags" "256" +"classname" "monster_wizard" +"origin" "1656 1496 968" +"angle" "180" +"target" "t41" +} +{ +"spawnflags" "2" +"classname" "trigger_teleport" +"target" "t40" +"targetname" "t39" +"model" "*29" +} +{ +"classname" "info_teleport_destination" +"origin" "984 1496 1000" +"angle" "180" +"targetname" "t40" +} +{ +"spawnflags" "256" +"classname" "path_corner" +"origin" "912 1496 1000" +"targetname" "t41" +"target" "t42" +} +{ +"spawnflags" "256" +"origin" "528 1368 1000" +"classname" "path_corner" +"targetname" "t42" +"target" "t41" +} +{ +"classname" "item_health" +"origin" "408 2640 520" +} +{ +"classname" "item_shells" +"origin" "456 2672 520" +"spawnflags" "1" +} +{ +"angle" "0" +"origin" "80 864 968" +"classname" "monster_wizard" +"target" "t44" +} +{ +"targetname" "t119" +"spawnflags" "2" +"classname" "trigger_teleport" +"target" "t45" +"model" "*30" +} +{ +"classname" "info_teleport_destination" +"origin" "432 856 952" +"angle" "45" +"targetname" "t45" +} +{ +"classname" "path_corner" +"origin" "496 872 952" +"target" "t43" +"targetname" "t44" +} +{ +"origin" "872 1056 952" +"classname" "path_corner" +"targetname" "t43" +"target" "t44" +} +{ +"classname" "trigger_once" +"spawnflags" "1792" +"target" "t39" +"model" "*31" +} +{ +"spawnflags" "1024" +"classname" "monster_knight" +"origin" "1168 56 904" +"angle" "135" +"target" "t49" +"targetname" "t67" +} +{ +"classname" "monster_ogre" +"origin" "1800 224 920" +"angle" "180" +"target" "t116" +"spawnflags" "256" +} +{ +"classname" "func_door" +"angle" "-1" +"wait" "-1" +"targetname" "t49" +"lip" "-24" +"model" "*32" +} +{ +"wait" "-1" +"angle" "-1" +"classname" "func_door" +"spawnflags" "1" +"targetname" "t39" +"lip" "-24" +"model" "*33" +} +{ +"classname" "func_door" +"angle" "-1" +"wait" "-1" +"targetname" "t29" +"lip" "-24" +"model" "*34" +} +{ +"spawnflags" "2" +"classname" "trigger_teleport" +"target" "t46" +"model" "*35" +} +{ +"classname" "info_teleport_destination" +"origin" "1104 232 880" +"angle" "180" +"targetname" "t46" +} +{ +"classname" "path_corner" +"origin" "1064 256 880" +"target" "t47" +"targetname" "t48" +"spawnflags" "256" +} +{ +"origin" "312 232 880" +"classname" "path_corner" +"targetname" "t47" +"target" "t48" +"spawnflags" "256" +} +{ +"classname" "info_teleport_destination" +"origin" "704 -40 1256" +"angle" "90" +"targetname" "t50" +} +{ +"classname" "trigger_once" +"target" "t52" +"model" "*36" +} +{ +"spawnflags" "2" +"classname" "trigger_teleport" +"target" "t50" +"targetname" "t52" +"model" "*37" +} +{ +"angle" "90" +"origin" "570 -898 1300" +"classname" "monster_wizard" +"targetname" "t52" +} +{ +"classname" "item_shells" +"origin" "216 120 880" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "192 232 880" +} +{ +"origin" "192 192 880" +"classname" "item_health" +"spawnflags" "1024" +} +{ +"classname" "item_shells" +"origin" "-216 1464 888" +} +{ +"classname" "item_health" +"origin" "816 1160 512" +"spawnflags" "1024" +} +{ +"spawnflags" "1" +"origin" "816 1120 512" +"classname" "item_health" +} +{ +"classname" "monster_wizard" +"origin" "944 840 956" +"angle" "135" +"target" "t53" +"targetname" "t64" +} +{ +"classname" "trigger_once" +"target" "t64" +"model" "*38" +} +{ +"classname" "monster_knight" +"origin" "704 392 1280" +"angle" "270" +"target" "t65" +"spawnflags" "1" +} +{ +"classname" "path_corner" +"origin" "704 208 1264" +"targetname" "t65" +"target" "t66" +} +{ +"origin" "704 496 1264" +"classname" "path_corner" +"targetname" "t66" +"target" "t65" +} +{ +"classname" "trigger_once" +"target" "t67" +"model" "*39" +} +{ +"sounds" "1" +"classname" "func_door" +"angle" "-2" +"wait" "-1" +"targetname" "t72" +"model" "*40" +} +{ +"sounds" "1" +"classname" "func_door" +"wait" "-1" +"angle" "-2" +"targetname" "t72" +"model" "*41" +} +{ +"classname" "trigger_once" +"target" "t72" +"model" "*42" +} +{ +"classname" "info_null" +"origin" "852 -580 820" +"targetname" "t73" +} +{ +"classname" "light" +"origin" "856 -584 936" +"light" "400" +"target" "t73" +} +{ +"classname" "light" +"origin" "920 -448 744" +"light" "150" +} +{ +"light" "150" +"origin" "760 -448 744" +"classname" "light" +} +{ +"classname" "light" +"origin" "552 -424 744" +"light" "150" +} +{ +"light" "150" +"origin" "560 -728 744" +"classname" "light" +} +{ +"classname" "light" +"origin" "776 -712 744" +"light" "150" +} +{ +"light" "150" +"origin" "936 -712 744" +"classname" "light" +} +{ +"classname" "path_corner" +"origin" "652 -576 952" +"targetname" "t75" +"target" "t74" +} +{ +"origin" "908 -576 952" +"classname" "path_corner" +"targetname" "t74" +"target" "t75" +} +{ +"classname" "monster_ogre" +"origin" "816 -260 952" +"angle" "270" +"targetname" "t72" +} +{ +"classname" "monster_ogre" +"origin" "724 -260 952" +"angle" "270" +"targetname" "t72" +"spawnflags" "256" +} +{ +"classname" "item_health" +"origin" "632 -548 820" +"spawnflags" "3072" +} +{ +"origin" "672 -548 820" +"classname" "item_health" +} +{ +"classname" "func_button" +"angle" "-2" +"wait" "-1" +"target" "t76" +"model" "*43" +} +{ +"sounds" "1" +"classname" "func_door" +"angle" "-1" +"wait" "-1" +"targetname" "t76" +"model" "*44" +} +{ +"light" "150" +"origin" "1040 -712 1016" +"classname" "light" +} +{ +"origin" "1112 -576 942" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light" +"origin" "1064 -576 896" +"light" "150" +} +{ +"classname" "light" +"origin" "888 -80 968" +"light" "175" +} +{ +"light" "175" +"origin" "512 -80 968" +"classname" "light" +} +{ +"classname" "item_armor2" +"origin" "1184 -96 920" +} +{ +"classname" "monster_ogre" +"origin" "392 8 912" +"angle" "315" +"targetname" "t77" +"spawnflags" "256" +} +{ +"classname" "trigger_once" +"target" "t77" +"model" "*45" +} +{ +"classname" "item_health" +"origin" "336 -224 888" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "968 16 888" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "560 2808 516" +"spawnflags" "1" +} +{ +"classname" "path_corner" +"origin" "1056 -384 824" +"targetname" "t78" +"target" "t79" +"spawnflags" "256" +} +{ +"origin" "1056 -736 824" +"classname" "path_corner" +"targetname" "t79" +"target" "t78" +"spawnflags" "256" +} +{ +"classname" "monster_ogre" +"origin" "1064 -656 840" +"angle" "90" +"target" "t78" +"spawnflags" "257" +} +{ +"angle" "90" +"origin" "848 -880 952" +"classname" "monster_ogre" +"targetname" "t72" +} +{ +"classname" "item_shells" +"origin" "1160 16 1248" +} +{ +"classname" "item_health" +"origin" "280 64 1248" +} +{ +"classname" "item_rockets" +"origin" "648 -256 928" +"spawnflags" "1025" +} +{ +"classname" "item_spikes" +"origin" "648 -304 1248" +} +{ +"origin" "696 -304 1248" +"classname" "item_spikes" +} +{ +"classname" "light_flame_small_yellow" +"origin" "768 -352 1230" +} +{ +"target" "t83" +"wait" ".8" +"classname" "trigger_multiple" +"model" "*46" +} +{ +"target" "t83" +"classname" "trigger_multiple" +"wait" ".8" +"model" "*47" +} +{ +"target" "t83" +"wait" ".8" +"classname" "trigger_multiple" +"model" "*48" +} +{ +"target" "t83" +"classname" "trigger_multiple" +"wait" ".8" +"model" "*49" +} +{ +"target" "t83" +"wait" ".8" +"classname" "trigger_multiple" +"model" "*50" +} +{ +"targetname" "t83" +"classname" "trap_spikeshooter" +"origin" "1108 -576 1140" +"spawnflags" "1" +"angle" "180" +} +{ +"targetname" "t83" +"angle" "90" +"spawnflags" "1" +"origin" "768 -796 1140" +"classname" "trap_spikeshooter" +} +{ +"origin" "1112 -576 1230" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light_flame_small_yellow" +"origin" "768 -800 1230" +} +{ +"classname" "light" +"origin" "712 -756 1168" +"light" "175" +} +{ +"light" "175" +"origin" "768 -424 1168" +"classname" "light" +} +{ +"light" "175" +"origin" "888 -744 956" +"classname" "light" +} +{ +"classname" "light" +"origin" "888 -408 956" +"light" "175" +} +{ +"origin" "384 -224 888" +"classname" "item_shells" +} +{ +"origin" "584 1784 920" +"classname" "item_health" +} +{ +"origin" "832 2064 920" +"classname" "item_shells" +} +{ +"target" "t72" +"classname" "trigger_once" +"model" "*51" +} +{ +"target" "t85" +"targetname" "t84" +"origin" "1560 216 896" +"classname" "path_corner" +} +{ +"target" "t48" +"targetname" "t85" +"classname" "path_corner" +"origin" "1456 216 896" +} +{ +"origin" "704 1368 516" +"classname" "weapon_supernailgun" +} +{ +"spawnflags" "1" +"origin" "184 1928 920" +"classname" "item_spikes" +} +{ +"classname" "item_spikes" +"origin" "656 1816 528" +"spawnflags" "768" +} +{ +"classname" "item_shells" +"origin" "1072 -800 820" +"spawnflags" "1024" +} +{ +"classname" "monster_ogre" +"origin" "840 -40 1276" +"angle" "180" +"targetname" "t86" +"spawnflags" "768" +} +{ +"classname" "trigger_once" +"target" "t86" +"model" "*52" +} +{ +"classname" "light" +"origin" "472 -576 876" +"light" "150" +} +{ +"classname" "item_shells" +"origin" "656 680 1256" +"spawnflags" "1536" +} +{ +"angle" "270" +"origin" "880 2224 536" +"classname" "monster_knight" +"spawnflags" "256" +} +{ +"angle" "180" +"origin" "1112 2424 944" +"classname" "monster_ogre" +"spawnflags" "1281" +} +{ +"targetname" "t88" +"target" "t87" +"origin" "360 384 880" +"classname" "path_corner" +"spawnflags" "1280" +} +{ +"target" "t88" +"targetname" "t87" +"classname" "path_corner" +"origin" "504 160 880" +"spawnflags" "1280" +} +{ +"target" "t87" +"angle" "315" +"origin" "384 320 896" +"classname" "monster_knight" +"spawnflags" "1280" +} +{ +"spawnflags" "257" +"targetname" "t86" +"angle" "0" +"origin" "376 120 1272" +"classname" "monster_ogre" +} +{ +"origin" "776 1368 916" +"classname" "item_shells" +"spawnflags" "1024" +} +{ +"classname" "item_spikes" +"origin" "184 1968 920" +"spawnflags" "1" +} +{ +"classname" "monster_wizard" +"origin" "312 936 944" +"angle" "45" +"spawnflags" "769" +} +{ +"classname" "monster_knight" +"origin" "704 -80 908" +"angle" "90" +} +{ +"angle" "0" +"origin" "568 -56 1276" +"classname" "monster_ogre" +"targetname" "t86" +"spawnflags" "768" +} +{ +"spawnflags" "1536" +"targetname" "t90" +"target" "t89" +"origin" "720 1696 924" +"classname" "path_corner" +} +{ +"spawnflags" "1536" +"target" "t90" +"targetname" "t89" +"classname" "path_corner" +"origin" "720 1416 924" +} +{ +"spawnflags" "1537" +"target" "t90" +"origin" "704 1784 948" +"classname" "monster_ogre" +} +{ +"classname" "func_door_secret" +"angle" "0" +"spawnflags" "1" +"targetname" "t91" +"model" "*53" +"t_length" "73" // svdijk -- added to prevent z-fighting +} +{ +"classname" "func_button" +"angle" "-2" +"wait" "-1" +"target" "t92" +"model" "*54" +} +{ +"classname" "func_button" +"angle" "-2" +"wait" "-1" +"target" "t92" +"model" "*55" +} +{ +"classname" "func_button" +"wait" "-1" +"angle" "-2" +"target" "t92" +"model" "*56" +} +{ +"classname" "func_button" +"angle" "-2" +"wait" "-1" +"target" "t92" +"model" "*57" +} +{ +"classname" "func_button" +"wait" "-1" +"angle" "-2" +"target" "t92" +"model" "*58" +} +{ +"classname" "trigger_counter" +"count" "5" +"target" "t91" +"targetname" "t92" +"model" "*59" +} +{ +"classname" "light" +"origin" "680 -920 1144" +"light" "150" +} +{ +"classname" "item_spikes" +"origin" "752 -876 928" +"spawnflags" "1" +} +{ +"spawnflags" "3" +"angle" "0" +"classname" "func_door_secret" +"targetname" "t91" +"model" "*60" +"t_length" "73" // svdijk -- added to prevent z-fighting +} +{ +"light" "150" +"origin" "680 -256 1144" +"classname" "light" +} +{ +"classname" "trigger_once" +"spawnflags" "1792" +"target" "t72" +"model" "*61" +} +{ +"classname" "trigger_secret" +"sounds" "1" +"targetname" "t8" +"model" "*62" +} +{ +"angle" "270" +"origin" "704 624 1280" +"classname" "monster_knight" +"spawnflags" "1" +} +{ +"angle" "225" +"origin" "1016 -440 1128" +"classname" "monster_knight" +} +{ +"angle" "180" +"origin" "1024 -728 1128" +"classname" "monster_knight" +} +{ +"spawnflags" "256" +"angle" "180" +"origin" "1008 -568 1128" +"classname" "monster_knight" +} +{ +"spawnflags" "256" +"classname" "monster_knight" +"origin" "304 2312 952" +"angle" "0" +"targetname" "t29" +} +{ +"spawnflags" "1025" +"classname" "monster_knight" +"origin" "272 136 896" +"angle" "45" +} +{ +"classname" "monster_wizard" +"origin" "784 -576 960" +"angle" "0" +"target" "t74" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "732 -936 928" +} +{ +"origin" "772 -936 928" +"classname" "item_health" +} +{ +"spawnflags" "256" +"classname" "monster_knight" +"origin" "704 -248 1272" +"angle" "0" +} +{ +"classname" "path_corner" +"origin" "1328 928 1396" +"targetname" "t93" +"target" "t94" +"spawnflags" "512" +} +{ +"origin" "1176 928 1396" +"classname" "path_corner" +"target" "t93" +"targetname" "t94" +"spawnflags" "512" +} +{ +"classname" "monster_knight" +"origin" "1192 884 1412" +"angle" "0" +"target" "t93" +"spawnflags" "513" +} +{ +"classname" "monster_knight" +"origin" "704 2376 944" +"angle" "90" +} +{ +"classname" "monster_knight" +"origin" "888 2312 944" +"angle" "180" +"targetname" "t95" +} +{ +"angle" "0" +"origin" "512 2304 944" +"classname" "monster_knight" +"targetname" "t95" +"spawnflags" "768" +} +{ +"classname" "trigger_once" +"target" "t95" +"model" "*63" +} +{ +"spawnflags" "256" +"angle" "315" +"origin" "728 2312 536" +"classname" "monster_knight" +} +{ +"light" "200" +"origin" "1296 1528 936" +"classname" "light" +} +{ +"light" "250" +"origin" "1432 1360 982" +"classname" "light_flame_small_yellow" +} +{ +"light" "150" +"origin" "1400 1360 920" +"classname" "light" +} +{ +"light" "200" +"origin" "1248 1344 680" +"classname" "light" +} +{ +"lip" "-384" +"wait" "-1" +"angle" "90" +"classname" "func_door" +"targetname" "t98" +"model" "*64" +} +{ +"light" "150" +"origin" "1152 1328 584" +"classname" "light" +} +{ +"origin" "1376 1480 864" +"classname" "item_health" +} +{ +"sounds" "1" +"classname" "trigger_secret" +"model" "*65" +} +{ +"classname" "func_button" +"sounds" "1" +"angle" "0" +"wait" "-1" +"target" "t97" +"model" "*66" +} +{ +"classname" "func_button" +"wait" "-1" +"angle" "0" +"sounds" "1" +"target" "t97" +"model" "*67" +} +{ +"classname" "trigger_counter" +"targetname" "t97" +"target" "t98" +"model" "*68" +} +{ +"classname" "light" +"origin" "880 -888 968" +"light" "150" +} +{ +"light" "150" +"origin" "880 -264 968" +"classname" "light" +} +{ +"angle" "180" +"origin" "1112 2344 944" +"classname" "monster_ogre" +"spawnflags" "769" +} +{ +"angle" "270" +"origin" "1120 880 1412" +"classname" "monster_ogre" +"spawnflags" "257" +} +{ +"map" "e1m8" +"classname" "trigger_changelevel" +"model" "*69" +} +{ +"light" "175" +"origin" "824 -756 1168" +"classname" "light" +} +{ +"classname" "light" +"origin" "1080 -528 1168" +"light" "175" +} +{ +"classname" "monster_wizard" +"origin" "672 -392 1024" +"angle" "315" +"spawnflags" "257" +} +{ +"classname" "monster_knight" +"origin" "1008 -656 1128" +"angle" "180" +"spawnflags" "768" +} +{ +"origin" "520 1064 440" +"classname" "air_bubbles" +} +{ +"classname" "item_spikes" +"origin" "16 1432 892" +} +{ +"classname" "trigger_once" +"message" "A secret cave has opened..." +"targetname" "t98" +"model" "*70" +} +{ +"target" "t4" +"health" "1" +"wait" "-1" +"angle" "0" +"classname" "func_button" +"model" "*71" +} +{ +"target" "t2" +"health" "1" +"wait" "-1" +"angle" "180" +"classname" "func_button" +"model" "*72" +} +{ +"target" "t49" +"spawnflags" "769" +"angle" "135" +"origin" "1208 128 904" +"classname" "monster_demon1" +} +{ +"spawnflags" "769" +"angle" "45" +"origin" "288 160 896" +"classname" "monster_demon1" +} +{ +"spawnflags" "768" +"classname" "monster_ogre" +"origin" "692 -884 952" +"angle" "90" +} +{ +"classname" "monster_ogre" +"origin" "-312 1648 1372" +"angle" "90" +"targetname" "t23" +"spawnflags" "768" +} +{ +"angle" "90" +"origin" "-192 1648 1372" +"classname" "monster_ogre" +"targetname" "t23" +"spawnflags" "768" +} +{ +"classname" "monster_ogre" +"origin" "704 1288 540" +"angle" "270" +"spawnflags" "768" +} +{ +"targetname" "t101" +"target" "t106" +"spawnflags" "770" +"classname" "trigger_teleport" +"model" "*73" +} +{ +"spawnflags" "768" +"targetname" "t101" +"angle" "270" +"origin" "-256 2424 1288" +"classname" "monster_wizard" +} +{ +"targetname" "t101" +"origin" "-248 2440 1280" +"classname" "trigger_relay" +} +{ +"targetname" "t29" +"spawnflags" "256" +"angle" "0" +"origin" "144 2648 1024" +"classname" "monster_wizard" +} +{ +"targetname" "t29" +"spawnflags" "768" +"classname" "monster_wizard" +"origin" "144 2592 1024" +"angle" "0" +} +{ +"targetname" "t29" +"spawnflags" "768" +"angle" "0" +"origin" "144 2536 1024" +"classname" "monster_wizard" +} +{ +"targetname" "t29" +"target" "t102" +"spawnflags" "258" +"classname" "trigger_teleport" +"model" "*74" +} +{ +"targetname" "t29" +"target" "t103" +"spawnflags" "770" +"classname" "trigger_teleport" +"model" "*75" +} +{ +"targetname" "t29" +"target" "t104" +"spawnflags" "770" +"classname" "trigger_teleport" +"model" "*76" +} +{ +"angle" "270" +"targetname" "t102" +"spawnflags" "256" +"origin" "704 2656 1008" +"classname" "info_teleport_destination" +} +{ +"angle" "270" +"targetname" "t103" +"spawnflags" "768" +"classname" "info_teleport_destination" +"origin" "920 2520 1008" +} +{ +"angle" "0" +"targetname" "t104" +"spawnflags" "768" +"origin" "592 2192 1008" +"classname" "info_teleport_destination" +} +{ +"sounds" "1" +"classname" "func_door" +"angle" "0" +"wait" "-1" +"speed" "150" +"targetname" "t105" +"model" "*77" +} +{ +"classname" "monster_demon1" +"origin" "1056 -880 1128" +"angle" "90" +"spawnflags" "768" +"targetname" "t105" +} +{ +"classname" "trigger_once" +"spawnflags" "768" +"target" "t105" +"model" "*78" +} +{ +"classname" "light" +"origin" "1056 -920 1184" +"light" "125" +} +{ +"classname" "trigger_relay" +"origin" "1104 -864 1120" +"target" "t105" +} +{ +"classname" "monster_ogre" +"origin" "1120 768 1416" +"angle" "180" +"spawnflags" "769" +} +{ +"classname" "air_bubbles" +"origin" "884 1616 440" +} +{ +"targetname" "t106" +"spawnflags" "768" +"angle" "270" +"origin" "-264 2232 1296" +"classname" "info_teleport_destination" +} +{ +"classname" "path_corner" +"origin" "568 1984 928" +"targetname" "t107" +"target" "t108" +"spawnflags" "256" +} +{ +"origin" "424 1960 928" +"classname" "path_corner" +"target" "t107" +"spawnflags" "256" +"targetname" "t111" +} +{ +"classname" "path_corner" +"origin" "704 2024 928" +"targetname" "t108" +"target" "t109" +"spawnflags" "256" +} +{ +"origin" "712 1712 928" +"classname" "path_corner" +"targetname" "t109" +"target" "t110" +"spawnflags" "256" +} +{ +"classname" "path_corner" +"origin" "712 1416 928" +"targetname" "t110" +"target" "t109" +"spawnflags" "256" +} +{ +"classname" "func_door" +"angle" "-2" +"wait" "-1" +"lip" "-24" +"targetname" "t26" +"spawnflags" "2304" +"model" "*79" +} +{ +"classname" "monster_ogre" +"origin" "240 2048 944" +"angle" "0" +"spawnflags" "257" +"target" "t111" +} +{ +"target" "t23" +"spawnflags" "1792" +"classname" "trigger_once" +"model" "*80" +} +{ +"classname" "monster_knight" +"origin" "576 2768 536" +"angle" "0" +"spawnflags" "256" +} +{ +"spawnflags" "256" +"angle" "180" +"origin" "824 2776 536" +"classname" "monster_knight" +} +{ +"classname" "monster_wizard" +"origin" "704 -1032 1024" +"angle" "90" +"spawnflags" "256" +"targetname" "t52" +} +{ +"angle" "90" +"origin" "760 -1032 1024" +"classname" "monster_wizard" +"spawnflags" "768" +"targetname" "t114" +} +{ +"classname" "monster_wizard" +"origin" "816 -1032 1024" +"angle" "90" +"spawnflags" "768" +"targetname" "t114" +} +{ +"targetname" "t52" +"classname" "trigger_teleport" +"spawnflags" "258" +"target" "t112" +"model" "*81" +} +{ +"classname" "trigger_teleport" +"spawnflags" "770" +"target" "t113" +"targetname" "t114" +"model" "*82" +} +{ +"targetname" "t114" +"classname" "trigger_teleport" +"spawnflags" "770" +"target" "t115" +"model" "*83" +} +{ +"classname" "info_teleport_destination" +"origin" "896 224 1352" +"angle" "135" +"spawnflags" "256" +"targetname" "t112" +} +{ +"classname" "info_teleport_destination" +"origin" "488 1648 1016" +"angle" "315" +"spawnflags" "768" +"targetname" "t113" +} +{ +"classname" "trigger_once" +"spawnflags" "768" +"target" "t114" +"model" "*84" +} +{ +"classname" "info_teleport_destination" +"origin" "800 904 928" +"angle" "90" +"spawnflags" "768" +"targetname" "t115" +} +{ +"angle" "270" +"origin" "-256 2232 1242" +"classname" "info_player_deathmatch" +} +{ +"spawnflags" "1792" +"origin" "-256 2096 1218" +"classname" "weapon_supershotgun" +} +{ +"angle" "270" +"origin" "704 424 1266" +"classname" "info_player_deathmatch" +} +{ +"angle" "270" +"origin" "704 2488 946" +"classname" "info_player_deathmatch" +} +{ +"angle" "270" +"origin" "704 1968 546" +"classname" "info_player_deathmatch" +} +{ +"angle" "90" +"origin" "704 104 898" +"classname" "info_player_deathmatch" +} +{ +"angle" "270" +"origin" "704 1568 938" +"classname" "info_player_deathmatch" +} +{ +"spawnflags" "1792" +"origin" "704 1344 912" +"classname" "weapon_rocketlauncher" +} +{ +"angle" "0" +"origin" "712 -576 840" +"classname" "info_player_deathmatch" +} +{ +"spawnflags" "1792" +"origin" "944 -576 816" +"classname" "weapon_nailgun" +} +{ +"angle" "180" +"origin" "1064 -576 1128" +"classname" "info_player_deathmatch" +} +{ +"spawnflags" "1792" +"origin" "696 584 1256" +"classname" "weapon_grenadelauncher" +} +{ +"classname" "light" +"origin" "316 804 780" +"light" "150" +} +{ +"classname" "light" +"origin" "316 804 644" +"light" "75" +} +{ +"classname" "item_rockets" +"origin" "298 710 706" +"spawnflags" "1" +} +{ +"classname" "item_shells" +"origin" "988 -928 1104" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "-56 2112 1220" +"spawnflags" "3584" +} +{ +"classname" "item_health" +"origin" "-56 2072 1220" +"spawnflags" "2305" +} +{ +"spawnflags" "1" +"origin" "584 2416 512" +"classname" "item_spikes" +} +{ +"origin" "688 1392 516" +"classname" "item_spikes" +} +{ +"classname" "item_artifact_invulnerability" +"origin" "712 2312 948" +"spawnflags" "1792" +} +{ +"origin" "32 1392 916" +"classname" "item_artifact_envirosuit" +} +{ +"mangle" "26 310 0" +"origin" "384 488 1552" +"classname" "info_intermission" +} +{ +"light" "100" +"origin" "800 1160 464" +"classname" "light" +} +{ +"classname" "light" +"origin" "608 1160 464" +"light" "100" +} +{ +"light" "100" +"origin" "604 1472 464" +"classname" "light" +} +{ +"target" "t40" +"classname" "trigger_teleport" +"model" "*85" +} +{ +"mangle" "-20 75 0" +"origin" "456 2144 568" +"classname" "info_intermission" +} +{ +"mangle" "10 80 0" +"origin" "464 1032 1000" +"classname" "info_intermission" +} +{ +"mangle" "20 135 0" +"origin" "1080 -752 1008" +"classname" "info_intermission" +} +{ +"classname" "trigger_secret" +"model" "*86" +} +{ +"classname" "path_corner" +"origin" "1632 216 896" +"targetname" "t116" +"target" "t84" +} +{ +"classname" "item_spikes" +"origin" "1200 48 872" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "928 2664 320" +"spawnflags" "1" +} +{ +"classname" "item_shells" +"origin" "1240 768 1384" +"spawnflags" "1" +} +{ +"classname" "item_shells" +"origin" "-88 2160 1224" +"spawnflags" "2049" +} +{ +"classname" "info_player_coop" +"origin" "-208 2272 1240" +"angle" "270" +} +{ +"angle" "270" +"origin" "-304 2272 1240" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "-352 2272 1240" +"angle" "270" +} +{ +"light" "200" +"origin" "1288 1648 956" +"classname" "light" +} +{ +"classname" "item_spikes" +"origin" "-264 1464 888" +} +{ +"spawnflags" "1792" +"origin" "1296 1488 888" +"classname" "item_artifact_invisibility" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*87" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*88" +} +{ +"target" "t118" +"targetname" "t117" +"origin" "-176 1640 888" +"classname" "path_corner" +} +{ +"targetname" "t118" +"target" "t117" +"classname" "path_corner" +"origin" "-320 1640 888" +} +{ +"target" "t117" +"origin" "-256 1632 904" +"classname" "monster_knight" +"spawnflags" "1" +} +{ +"origin" "1376 1424 864" +"classname" "weapon_grenadelauncher" +} +{ +"spawnflags" "1" +"targetname" "t120" +"target" "t119" +"classname" "trigger_counter" +"model" "*89" +} +{ +"target" "t120" +"targetname" "t53" +"classname" "trigger_once" +"model" "*90" +} +{ +"target" "t120" +"targetname" "t39" +"classname" "trigger_once" +"model" "*91" +} +{ +"classname" "light" +"origin" "480 2568 568" +"light" "125" +} +{ +"origin" "162 1482 976" +"classname" "ambient_drip" +} +{ +"origin" "786 1010 584" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "778 1210 584" +} +{ +"origin" "594 1202 584" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "602 1010 584" +} +{ +"origin" "786 1514 584" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "794 1698 584" +} +{ +"origin" "618 1690 584" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "618 1522 584" +} +{ +"origin" "698 1362 584" +"classname" "ambient_drip" +} +{ +"origin" "714 1970 592" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "898 2170 592" +} +{ +"origin" "938 2346 592" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "682 2298 592" +} +{ +"origin" "458 2306 592" +"classname" "ambient_drip" +} +{ +"origin" "458 1690 880" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "322 1506 880" +} +{ +"origin" "338 1226 880" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "466 1090 880" +} +{ +"origin" "394 882 880" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "674 810 880" +} +{ +"origin" "914 818 880" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "922 1034 880" +} +{ +"origin" "1082 1266 880" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "994 1442 880" +} +{ +"origin" "898 1714 880" +"classname" "ambient_drip" +} +{ +"origin" "706 1362 1080" +"classname" "ambient_drip" +} +{ +"origin" "1194 1522 1032" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "1314 1354 1032" +} +{ +"origin" "442 354 920" +"classname" "ambient_swamp1" +} +{ +"origin" "978 314 920" +"classname" "ambient_swamp2" +} diff --git a/Misc/qs_pak/maps/e2m2.diff b/Misc/qs_pak/maps/e2m2.diff new file mode 100644 index 0000000..4dbf2dc --- /dev/null +++ b/Misc/qs_pak/maps/e2m2.diff @@ -0,0 +1,54 @@ +--- e2m2.ent ++++ e2m2@fbfe.ent +@@ -908,20 +908,21 @@ + { + "classname" "func_door" + "angle" "-2" + "spawnflags" "33" + "speed" "10" + "sounds" "3" + "wait" "-1" + "targetname" "t16" + "dmg" "100" + "model" "*13" ++"lip" "7" // svdijk -- added to prevent z-fighting + } + { + "sounds" "3" + "classname" "func_door" + "angle" "90" + "spawnflags" "2056" + "wait" "-1" + "model" "*14" + } + { +@@ -1423,27 +1424,27 @@ + { + "target" "t39" + "targetname" "t40" + "origin" "-216 280 104" + "classname" "path_corner" + } + { + "target" "t40" + "targetname" "t38" + "classname" "path_corner" +-"origin" "-16 280 104" ++"origin" "-15 280 104" // svdijk -- changed to prevent z-fighting (was "-16 280 104") + } + { + "target" "t38" + "wait" "-1" + "targetname" "t39" +-"origin" "-16 280 104" ++"origin" "-15 280 104" // svdijk -- changed to prevent z-fighting (was "-16 280 104") + "classname" "path_corner" + } + { + "angle" "180" + "origin" "680 1472 24" + "classname" "monster_ogre" + "spawnflags" "256" + } + { + "spawnflags" "256" diff --git a/Misc/qs_pak/maps/e2m2@fbfe.ent b/Misc/qs_pak/maps/e2m2@fbfe.ent new file mode 100644 index 0000000..82dc0da --- /dev/null +++ b/Misc/qs_pak/maps/e2m2@fbfe.ent @@ -0,0 +1,2005 @@ +{ +"message" "the Ogre Citadel" +"sounds" "8" +"wad" "gfx/wizard.wad" +"classname" "worldspawn" +"worldtype" "0" +} +{ +"origin" "160 -160 120" +"classname" "light" +} +{ +"angle" "90" +"origin" "-256 -1952 280" +"classname" "info_player_start" +} +{ +"classname" "light" +"origin" "160 -392 248" +"light" "200" +} +{ +"classname" "light" +"origin" "160 -648 184" +} +{ +"classname" "light" +"origin" "-56 -392 248" +"light" "200" +} +{ +"classname" "light" +"origin" "376 -392 248" +"light" "200" +} +{ +"classname" "light" +"origin" "288 -416 -72" +"light" "200" +} +{ +"classname" "light" +"origin" "32 -416 -72" +"light" "200" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "10 -270 148" +"light" "250" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "314 -270 148" +"light" "250" +} +{ +"classname" "light" +"origin" "-264 -440 248" +"light" "200" +} +{ +"classname" "light" +"origin" "584 -440 248" +"light" "200" +} +{ +"classname" "light" +"origin" "544 -648 248" +} +{ +"light" "150" +"origin" "648 -456 -184" +"classname" "light" +} +{ +"origin" "376 -800 184" +"classname" "light" +} +{ +"classname" "light" +"origin" "-152 -752 184" +"light" "250" +} +{ +"classname" "light" +"origin" "-232 -592 184" +"light" "250" +} +{ +"classname" "light" +"origin" "160 112 184" +} +{ +"classname" "light" +"origin" "160 352 120" +} +{ +"classname" "light" +"origin" "160 216 -48" +"light" "120" +} +{ +"classname" "light" +"origin" "160 16 -48" +"light" "120" +} +{ +"classname" "light" +"origin" "160 544 144" +"light" "225" +} +{ +"classname" "light" +"origin" "480 576 88" +} +{ +"classname" "light" +"origin" "480 448 88" +} +{ +"classname" "light" +"origin" "480 576 168" +"light" "250" +} +{ +"classname" "light" +"origin" "480 448 168" +"light" "250" +} +{ +"classname" "light" +"origin" "160 896 312" +"light" "350" +} +{ +"classname" "light" +"origin" "288 896 312" +"light" "100" +} +{ +"classname" "light" +"origin" "32 896 312" +"light" "100" +} +{ +"classname" "light" +"origin" "160 1008 312" +"light" "100" +} +{ +"classname" "light" +"origin" "160 784 312" +"light" "100" +} +{ +"classname" "light" +"origin" "392 120 184" +"light" "350" +} +{ +"classname" "light" +"origin" "568 208 184" +"light" "350" +} +{ +"classname" "light" +"origin" "720 480 184" +} +{ +"classname" "light" +"origin" "640 632 184" +} +{ +"classname" "light" +"origin" "472 1152 56" +} +{ +"classname" "light" +"origin" "512 896 152" +} +{ +"origin" "800 800 184" +"classname" "light" +} +{ +"light" "200" +"origin" "632 1264 -40" +"classname" "light" +} +{ +"origin" "800 1032 184" +"classname" "light" +} +{ +"origin" "760 1472 64" +"classname" "light" +} +{ +"light" "200" +"origin" "544 1416 56" +"classname" "light" +} +{ +"origin" "672 1256 184" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"origin" "1024 1272 184" +"classname" "light" +} +{ +"light" "200" +"origin" "992 1440 184" +"classname" "light" +} +{ +"origin" "1240 488 184" +"classname" "light" +} +{ +"origin" "1280 136 176" +"classname" "light" +} +{ +"origin" "160 1304 136" +"classname" "light" +} +{ +"origin" "160 1648 256" +"classname" "light" +"light" "200" +} +{ +"origin" "240 1600 256" +"classname" "light" +"light" "200" +} +{ +"origin" "16 1616 168" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"origin" "-120 1304 40" +"classname" "light" +} +{ +"origin" "-352 1144 16" +"classname" "light" +"light" "250" +} +{ +"origin" "-56 1096 64" +"classname" "light" +} +{ +"light" "200" +"origin" "-56 1152 280" +"classname" "light" +} +{ +"light" "350" +"origin" "-440 1144 200" +"classname" "light" +} +{ +"light" "200" +"origin" "-352 1336 -72" +"classname" "light" +} +{ +"origin" "-488 368 56" +"classname" "light" +"light" "250" +} +{ +"light" "350" +"origin" "-488 896 136" +"classname" "light" +} +{ +"origin" "-216 896 184" +"classname" "light" +"light" "250" +} +{ +"origin" "-128 536 168" +"classname" "light" +"light" "200" +} +{ +"light" "150" +"origin" "-104 480 32" +"classname" "light" +} +{ +"light" "150" +"origin" "-208 936 56" +"classname" "light" +} +{ +"light" "150" +"origin" "-208 736 32" +"classname" "light" +} +{ +"origin" "-344 64 184" +"classname" "light" +} +{ +"light" "350" +"origin" "-648 384 184" +"classname" "light" +} +{ +"light" "350" +"origin" "-488 688 184" +"classname" "light" +} +{ +"light" "150" +"origin" "-680 496 -36" +"classname" "light" +} +{ +"light" "350" +"origin" "-600 1104 96" +"classname" "light" +} +{ +"origin" "-824 896 168" +"classname" "light" +} +{ +"origin" "-896 600 160" +"classname" "light" +} +{ +"target" "t1" +"classname" "trigger_teleport" +"model" "*1" +} +{ +"spawnflags" "1792" +"targetname" "t1" +"origin" "-448 264 -56" +"classname" "info_teleport_destination" +"angle" "45" +} +{ +"light" "250" +"origin" "-168 1320 160" +"classname" "light" +} +{ +"origin" "-24 896 184" +"classname" "light" +} +{ +"origin" "-104 896 184" +"classname" "light" +"light" "250" +} +{ +"spawnflags" "2" +"origin" "-680 880 48" +"classname" "item_health" +} +{ +"angle" "90" +"origin" "-896 512 24" +"classname" "info_player_deathmatch" +} +{ +"angle" "90" +"origin" "-184 560 128" +"classname" "info_player_deathmatch" +} +{ +"angle" "270" +"origin" "160 1640 160" +"classname" "info_player_deathmatch" +} +{ +"angle" "90" +"origin" "1272 112 80" +"classname" "info_player_deathmatch" +} +{ +"angle" "90" +"origin" "176 -784 24" +"classname" "info_player_deathmatch" +} +{ +"spawnflags" "1" +"origin" "208 -160 0" +"classname" "item_rockets" +} +{ +"origin" "64 464 0" +"classname" "item_health" +} +{ +"origin" "120 464 0" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "-8 840 64" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "-8 952 64" +"classname" "item_health" +} +{ +"spawnflags" "9" +"origin" "568 880 0" +"classname" "item_weapon" +} +{ +"origin" "456 1104 -88" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "664 1272 -88" +"classname" "item_shells" +} +{ +"angle" "225" +"origin" "888 1312 120" +"classname" "info_player_deathmatch" +} +{ +"origin" "1328 168 56" +"classname" "item_health" +} +{ +"origin" "1328 128 56" +"classname" "item_health" +} +{ +"origin" "1328 208 56" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "816 1472 0" +"classname" "item_shells" +} +{ +"light" "200" +"origin" "-64 -120 -56" +"classname" "light" +} +{ +"light" "200" +"origin" "-296 -160 -56" +"classname" "light" +} +{ +"light" "200" +"origin" "-296 -208 -272" +"classname" "light" +} +{ +"classname" "func_plat" +"model" "*2" +} +{ +"spawnflags" "2" +"origin" "-192 -176 -80" +"classname" "item_health" +} +{ +"light" "350" +"origin" "-816 128 184" +"classname" "light" +} +{ +"light" "350" +"origin" "-664 -80 184" +"classname" "light" +} +{ +"light" "250" +"origin" "-656 112 32" +"classname" "light" +} +{ +"origin" "-352 208 56" +"classname" "light" +"light" "250" +} +{ +"origin" "-184 504 24" +"classname" "light" +"light" "250" +} +{ +"classname" "item_armor1" +"origin" "400 -888 0" +} +{ +"spawnflags" "1792" +"classname" "item_armorInv" +"origin" "-104 448 104" +} +{ +"classname" "item_armor2" +"origin" "-680 496 32" +} +{ +"classname" "weapon_grenadelauncher" +"origin" "168 1608 136" +"spawnflags" "1792" +} +{ +"classname" "weapon_rocketlauncher" +"origin" "1232 176 56" +"spawnflags" "1792" +} +{ +"classname" "weapon_supernailgun" +"origin" "-960 944 40" +"spawnflags" "1792" +} +{ +"classname" "weapon_supershotgun" +"origin" "752 1464 0" +"spawnflags" "1792" +} +{ +"classname" "weapon_nailgun" +"origin" "160 120 24" +"spawnflags" "1792" +} +{ +"classname" "item_rockets" +"origin" "-224 792 104" +"spawnflags" "1792" +} +{ +"classname" "item_rockets" +"origin" "-640 120 -56" +"spawnflags" "1792" +} +{ +"light" "200" +"origin" "-70 -1126 212" +"classname" "light_torch_small_walltorch" +} +{ +"light" "200" +"classname" "light_torch_small_walltorch" +"origin" "186 -1134 148" +} +{ +"light" "200" +"origin" "138 -966 100" +"classname" "light_torch_small_walltorch" +} +{ +"light" "200" +"classname" "light_torch_small_walltorch" +"origin" "-150 -1318 260" +} +{ +"light" "150" +"origin" "184 -880 72" +"classname" "light" +} +{ +"style" "6" +"light" "200" +"origin" "-46 -1742 340" +"classname" "light_torch_small_walltorch" +} +{ +"style" "1" +"light" "200" +"classname" "light_torch_small_walltorch" +"origin" "-486 -1726 340" +} +{ +"origin" "-272 -1544 384" +"classname" "light" +"light" "200" +} +{ +"style" "6" +"light" "200" +"classname" "light_torch_small_walltorch" +"origin" "-414 -2006 340" +} +{ +"style" "1" +"light" "200" +"origin" "-134 -1958 340" +"classname" "light_torch_small_walltorch" +} +{ +"light" "150" +"origin" "-472 -1840 328" +"classname" "light" +} +{ +"classname" "light" +"origin" "-72 -1592 328" +"light" "150" +} +{ +"light" "150" +"origin" "-320 -1424 264" +"classname" "light" +} +{ +"light" "150" +"origin" "-256 -1952 280" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "-272 -1720 312" +} +{ +"origin" "-232 -1280 168" +"classname" "path_corner" +"targetname" "t5" +"target" "t6" +} +{ +"classname" "path_corner" +"origin" "-64 -1176 120" +"targetname" "t6" +"target" "t7" +} +{ +"origin" "152 -1104 56" +"classname" "path_corner" +"targetname" "t7" +"target" "t8" +} +{ +"classname" "path_corner" +"origin" "192 -952 8" +"targetname" "t8" +"target" "t9" +} +{ +"origin" "184 -808 8" +"classname" "path_corner" +"target" "t3" +"targetname" "t9" +} +{ +"classname" "path_corner" +"origin" "512 -776 8" +"targetname" "t3" +"target" "t10" +} +{ +"origin" "-200 -648 8" +"classname" "path_corner" +"targetname" "t4" +"target" "t11" +} +{ +"classname" "monster_knight" +"origin" "-360 -1616 232" +"target" "t5" +"angle" "90" +} +{ +"origin" "512 -648 8" +"classname" "path_corner" +"targetname" "t10" +"target" "t4" +} +{ +"classname" "path_corner" +"origin" "-200 -776 8" +"targetname" "t11" +"target" "t3" +} +{ +"classname" "monster_knight" +"origin" "24 -632 24" +"spawnflags" "256" +"target" "t4" +} +{ +"classname" "monster_knight" +"origin" "336 -752 24" +"spawnflags" "256" +"target" "t3" +} +{ +"classname" "monster_knight" +"origin" "56 -712 24" +"angle" "270" +"spawnflags" "768" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "160 -712 24" +"classname" "monster_knight" +} +{ +"classname" "monster_knight" +"origin" "264 -712 24" +"angle" "270" +"spawnflags" "768" +} +{ +"classname" "item_health" +"origin" "-432 -1640 208" +"spawnflags" "1" +} +{ +"classname" "item_shells" +"origin" "-352 -592 0" +} +{ +"classname" "func_door" +"angle" "90" +"spawnflags" "1" +"targetname" "t13" +"wait" "-1" +"sounds" "3" +"dmg" "100" +"model" "*3" +} +{ +"health" "1" +"angle" "90" +"classname" "func_button" +"target" "t12" +"wait" "-1" +"sounds" "1" +"model" "*4" +} +{ +"classname" "func_button" +"angle" "90" +"health" "1" +"target" "t12" +"wait" "-1" +"sounds" "1" +"model" "*5" +} +{ +"classname" "trigger_counter" +"targetname" "t12" +"count" "2" +"target" "t13" +"model" "*6" +} +{ +"classname" "monster_demon1" +"origin" "160 -128 24" +"angle" "270" +"targetname" "t12" +} +{ +"classname" "light" +"origin" "-8 -288 64" +"light" "200" +} +{ +"light" "200" +"origin" "328 -288 64" +"classname" "light" +} +{ +"light" "200" +"origin" "-296 -448 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "160 -368 -248" +"light" "200" +} +{ +"light" "200" +"origin" "616 -448 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "408 -456 -248" +"light" "200" +} +{ +"light" "200" +"origin" "-40 -424 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "-280 -448 32" +"light" "200" +} +{ +"light" "200" +"origin" "632 -424 32" +"classname" "light" +} +{ +"classname" "light" +"origin" "160 -480 8" +"light" "150" +} +{ +"classname" "func_door" +"angle" "180" +"targetname" "t12" +"sounds" "3" +"speed" "200" +"wait" "-1" +"lip" "-2" +"model" "*7" +} +{ +"classname" "func_door" +"angle" "0" +"speed" "200" +"wait" "-1" +"lip" "-2" +"model" "*8" +} +{ +"classname" "light" +"origin" "160 -304 112" +"light" "170" +} +{ +"classname" "light" +"origin" "160 640 104" +"light" "200" +} +{ +"classname" "func_door" +"angle" "-1" +"targetname" "t14" +"sounds" "1" +"model" "*9" +} +{ +"classname" "trigger_multiple" +"target" "t14" +"wait" "10" +"model" "*10" +} +{ +"targetname" "t28" +"classname" "func_door" +"angle" "-1" +"wait" "-1" +"sounds" "1" +"speed" "200" +"spawnflags" "2048" +"model" "*11" +} +{ +"angle" "270" +"classname" "func_button" +"target" "t15" +"wait" "-1" +"lip" "2" +"sounds" "1" +"spawnflags" "2048" +"model" "*12" +} +{ +"classname" "light" +"origin" "-296 432 0" +"light" "200" +} +{ +"light" "200" +"origin" "-312 416 152" +"classname" "light" +} +{ +"classname" "light" +"origin" "-184 320 146" +"light" "200" +} +{ +"classname" "item_key2" +"origin" "-552 192 -40" +"sounds" "1" +"spawnflags" "2048" +} +{ +"classname" "item_spikes" +"origin" "-680 88 -48" +} +{ +"classname" "func_door" +"angle" "-2" +"spawnflags" "33" +"speed" "10" +"sounds" "3" +"wait" "-1" +"targetname" "t16" +"dmg" "100" +"model" "*13" +"lip" "7" // svdijk -- added to prevent z-fighting +} +{ +"sounds" "3" +"classname" "func_door" +"angle" "90" +"spawnflags" "2056" +"wait" "-1" +"model" "*14" +} +{ +"spawnflags" "2056" +"angle" "270" +"classname" "func_door" +"wait" "-1" +"model" "*15" +} +{ +"classname" "func_button" +"angle" "-2" +"wait" "-1" +"target" "t16" +"lip" "12" +"model" "*16" +} +{ +"classname" "light" +"origin" "888 1080 -104" +"light" "160" +} +{ +"light" "160" +"origin" "888 888 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "880 696 -104" +"light" "160" +} +{ +"light" "160" +"origin" "696 888 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "696 1080 -104" +"light" "160" +} +{ +"light" "200" +"origin" "696 672 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "568 512 -104" +"light" "200" +} +{ +"light" "160" +"origin" "840 504 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "832 328 -104" +"light" "160" +} +{ +"classname" "light" +"origin" "936 512 96" +"light" "200" +} +{ +"classname" "light" +"origin" "-248 1080 56" +"light" "150" +} +{ +"classname" "light" +"origin" "888 1264 176" +"light" "170" +} +{ +"classname" "func_door" +"angle" "-2" +"targetname" "t17" +"sounds" "1" +"model" "*17" +} +{ +"classname" "trigger_multiple" +"target" "t17" +"wait" "5" +"model" "*18" +} +{ +"classname" "light" +"origin" "160 120 -24" +"light" "160" +} +{ +"spawnflags" "256" +"classname" "trigger_multiple" +"target" "t18" +"targetname" "t23" +"model" "*19" +} +{ +"wait" "0.5" +"classname" "trap_spikeshooter" +"origin" "88 368 40" +"angle" "0" +"targetname" "t18" +} +{ +"classname" "func_wall" +"spawnflags" "2048" +"model" "*20" +} +{ +"classname" "monster_demon1" +"origin" "-160 608 128" +"targetname" "t19" +} +{ +"classname" "trigger_once" +"target" "t19" +"model" "*21" +} +{ +"classname" "light" +"origin" "-528 512 -104" +"light" "200" +} +{ +"light" "200" +"origin" "-344 592 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "-336 760 -104" +"light" "200" +} +{ +"light" "200" +"origin" "-432 856 -104" +"classname" "light" +} +{ +"classname" "monster_ogre" +"origin" "-416 440 -40" +"spawnflags" "1536" +"target" "t20" +} +{ +"classname" "monster_shambler" +"origin" "-272 296 -40" +"spawnflags" "256" +"target" "t20" +} +{ +"classname" "path_corner" +"origin" "-328 272 -56" +"targetname" "t20" +"target" "t21" +} +{ +"origin" "-400 480 -56" +"classname" "path_corner" +"target" "t20" +"targetname" "t21" +} +{ +"classname" "item_health" +"origin" "-600 144 -64" +} +{ +"classname" "weapon_supershotgun" +"origin" "440 512 0" +"spawnflags" "2048" +} +{ +"classname" "light" +"origin" "-120 176 184" +"light" "250" +} +{ +"origin" "-96 0 184" +"classname" "light" +"light" "200" +} +{ +"classname" "light" +"origin" "312 116 -48" +"light" "200" +} +{ +"classname" "path_corner" +"origin" "8 112 32" +"target" "t25" +"targetname" "t24" +} +{ +"origin" "312 112 32" +"classname" "path_corner" +"targetname" "t25" +"target" "t24" +} +{ +"classname" "monster_ogre" +"origin" "112 112 48" +"target" "t24" +"spawnflags" "256" +} +{ +"classname" "item_shells" +"origin" "88 -160 0" +"spawnflags" "1" +} +{ +"light" "250" +"classname" "light" +"origin" "-352 144 56" +} +{ +"classname" "monster_demon1" +"origin" "-80 -440 -296" +"spawnflags" "256" +"target" "t26" +} +{ +"classname" "path_corner" +"origin" "-216 -456 -312" +"targetname" "t26" +"target" "t27" +} +{ +"origin" "536 -448 -312" +"classname" "path_corner" +"target" "t26" +"targetname" "t27" +} +{ +"light" "200" +"origin" "320 232 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "312 0 -104" +"light" "200" +} +{ +"light" "160" +"origin" "-8 0 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "-8 232 -104" +"light" "200" +} +{ +"light" "200" +"origin" "-16 112 -104" +"classname" "light" +} +{ +"classname" "light" +"origin" "-248 -8 -104" +"light" "200" +} +{ +"light" "200" +"origin" "-472 120 -104" +"classname" "light" +} +{ +"wait" "-1" +"classname" "func_door" +"angle" "-1" +"targetname" "t15" +"sounds" "1" +"spawnflags" "2048" +"model" "*22" +} +{ +"classname" "func_door" +"angle" "-2" +"spawnflags" "2081" +"targetname" "t28" +"wait" "10" +"speed" "200" +"sounds" "1" +"model" "*23" +} +{ +"classname" "trigger_once" +"target" "t28" +"model" "*24" +} +{ +"classname" "trigger_once" +"target" "t12" +"model" "*25" +} +{ +"classname" "monster_ogre" +"origin" "160 1432 128" +"angle" "270" +} +{ +"classname" "monster_ogre" +"origin" "-216 784 128" +"angle" "90" +} +{ +"classname" "info_teleport_destination" +"origin" "-360 888 24" +"spawnflags" "2048" +"targetname" "t1" +} +{ +"classname" "monster_zombie" +"origin" "-288 -232 -296" +"angle" "270" +} +{ +"classname" "monster_zombie" +"origin" "168 -8 -104" +"angle" "90" +} +{ +"classname" "monster_ogre" +"origin" "648 1232 -64" +"angle" "180" +} +{ +"sounds" "1" +"classname" "func_door" +"angle" "-2" +"targetname" "t29" +"model" "*26" +} +{ +"classname" "monster_ogre" +"origin" "520 752 24" +"angle" "90" +"spawnflags" "256" +"targetname" "t29" +} +{ +"classname" "trigger_once" +"target" "t29" +"model" "*27" +} +{ +"classname" "weapon_nailgun" +"origin" "152 1608 136" +"spawnflags" "2048" +} +{ +"classname" "path_corner" +"origin" "544 896 8" +"targetname" "t30" +"target" "t31" +} +{ +"origin" "56 896 72" +"classname" "path_corner" +"target" "t30" +"targetname" "t31" +} +{ +"classname" "path_corner" +"origin" "168 552 8" +"targetname" "t32" +"target" "t33" +} +{ +"origin" "168 1392 8" +"classname" "path_corner" +"target" "t32" +"targetname" "t33" +} +{ +"classname" "monster_knight" +"origin" "240 584 24" +"target" "t32" +} +{ +"classname" "monster_knight" +"origin" "504 960 24" +"target" "t30" +"spawnflags" "256" +} +{ +"classname" "monster_knight" +"origin" "-16 896 88" +"angle" "0" +} +{ +"classname" "monster_knight" +"origin" "256 1224 24" +"angle" "225" +"spawnflags" "256" +} +{ +"target" "t49" +"origin" "-24 1064 16" +"classname" "monster_ogre" +} +{ +"spawnflags" "256" +"angle" "45" +"origin" "-184 1080 128" +"classname" "monster_ogre" +} +{ +"angle" "45" +"origin" "-256 1216 128" +"classname" "monster_knight" +} +{ +"targetname" "t16" +"angle" "270" +"origin" "784 520 56" +"classname" "monster_demon1" +} +{ +"classname" "path_corner" +"origin" "-352 888 16" +"targetname" "t34" +"target" "t35" +} +{ +"origin" "-120 888 8" +"classname" "path_corner" +"target" "t34" +"targetname" "t35" +} +{ +"classname" "monster_ogre" +"origin" "-184 912 24" +"target" "t34" +"spawnflags" "256" +} +{ +"classname" "monster_ogre" +"origin" "-720 896 64" +} +{ +"classname" "monster_knight" +"origin" "-344 760 24" +"angle" "135" +"spawnflags" "256" +} +{ +"classname" "monster_knight" +"origin" "-392 584 24" +"angle" "90" +"spawnflags" "256" +} +{ +"angle" "90" +"classname" "monster_knight" +"origin" "-528 528 24" +"spawnflags" "768" +} +{ +"targetname" "t37" +"target" "t36" +"origin" "-896 968 48" +"classname" "path_corner" +} +{ +"target" "t37" +"targetname" "t36" +"classname" "path_corner" +"origin" "-896 568 48" +} +{ +"spawnflags" "256" +"target" "t36" +"origin" "-840 600 24" +"classname" "monster_ogre" +} +{ +"style" "32" +"targetname" "t15" +"light" "200" +"origin" "-56 304 152" +"classname" "light" +} +{ +"dmg" "100" +"speed" "200" +"targetname" "t15" +"target" "t38" +"classname" "func_train" +"model" "*28" +} +{ +"target" "t39" +"targetname" "t40" +"origin" "-216 280 104" +"classname" "path_corner" +} +{ +"target" "t40" +"targetname" "t38" +"classname" "path_corner" +"origin" "-15 280 104" // svdijk -- changed to prevent z-fighting (was "-16 280 104") +} +{ +"target" "t38" +"wait" "-1" +"targetname" "t39" +"origin" "-15 280 104" // svdijk -- changed to prevent z-fighting (was "-16 280 104") +"classname" "path_corner" +} +{ +"angle" "180" +"origin" "680 1472 24" +"classname" "monster_ogre" +"spawnflags" "256" +} +{ +"spawnflags" "256" +"angle" "180" +"origin" "864 1448 24" +"classname" "monster_knight" +} +{ +"target" "t42" +"targetname" "t41" +"origin" "1024 1264 104" +"classname" "path_corner" +} +{ +"targetname" "t42" +"target" "t41" +"classname" "path_corner" +"origin" "704 1264 104" +} +{ +"target" "t41" +"origin" "1056 1320 120" +"classname" "monster_knight" +} +{ +"spawnflags" "257" +"origin" "552 1280 128" +"classname" "monster_knight" +} +{ +"spawnflags" "1" +"origin" "432 1280 128" +"classname" "monster_knight" +} +{ +"spawnflags" "2048" +"angle" "0" +"wait" "-1" +"sounds" "0" +"health" "1" +"target" "t28" +"classname" "func_button" +"model" "*29" +} +{ +"origin" "-72 296 104" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "-64 504 104" +} +{ +"origin" "-312 192 -64" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "-80 384 -64" +"classname" "item_spikes" +} +{ +"origin" "-224 976 104" +"classname" "item_shells" +} +{ +"target" "t666" +"classname" "trigger_multiple" +"wait" "10" +"model" "*30" +} +{ +"target" "t45" +"targetname" "t44" +"origin" "984 568 8" +"classname" "path_corner" +} +{ +"target" "t46" +"targetname" "t45" +"classname" "path_corner" +"origin" "976 464 8" +} +{ +"target" "t47" +"targetname" "t46" +"origin" "1224 448 40" +"classname" "path_corner" +} +{ +"target" "t44" +"targetname" "t43" +"classname" "path_corner" +"origin" "1272 520 40" +} +{ +"target" "t48" +"targetname" "t47" +"classname" "path_corner" +"origin" "1224 144 64" +} +{ +"targetname" "t48" +"target" "t43" +"origin" "1344 120 64" +"classname" "path_corner" +} +{ +"target" "t45" +"origin" "968 520 24" +"classname" "monster_ogre" +} +{ +"spawnflags" "256" +"target" "t43" +"origin" "1312 328 80" +"classname" "monster_knight" +} +{ +"spawnflags" "768" +"target" "t47" +"origin" "1240 296 80" +"classname" "monster_ogre" +} +{ +"origin" "-360 1064 -8" +"classname" "weapon_grenadelauncher" +} +{ +"target" "t50" +"targetname" "t49" +"origin" "-16 1168 0" +"classname" "path_corner" +} +{ +"targetname" "t50" +"target" "t49" +"classname" "path_corner" +"origin" "-232 1168 0" +} +{ +"origin" "1448 -128 -56" +"classname" "light" +} +{ +"wait" "-1" +"classname" "func_door" +"angle" "270" +"model" "*31" +} +{ +"wait" "-1" +"targetname" "t52" +"sounds" "3" +"classname" "func_door" +"angle" "90" +"model" "*32" +} +{ +"classname" "light" +"origin" "1984 -184 288" +"light" "350" +} +{ +"classname" "light" +"origin" "1760 -312 -184" +"light" "200" +} +{ +"origin" "1832 -64 -184" +"classname" "light" +"light" "200" +} +{ +"light" "160" +"origin" "1808 -296 -24" +"classname" "light" +} +{ +"classname" "light" +"origin" "1224 -8 -184" +"light" "160" +} +{ +"light" "160" +"origin" "1240 -208 -184" +"classname" "light" +} +{ +"classname" "light" +"origin" "1680 -104 -184" +"light" "160" +} +{ +"light" "160" +"origin" "1584 -304 -184" +"classname" "light" +} +{ +"origin" "1592 -72 288" +"classname" "light" +} +{ +"classname" "light" +"origin" "1328 -232 288" +} +{ +"classname" "light" +"origin" "1280 -16 327" +"light" "250" +} +{ +"classname" "light" +"origin" "1792 -152 88" +"light" "160" +} +{ +"light" "160" +"origin" "1632 -296 88" +"classname" "light" +} +{ +"origin" "1696 -280 288" +"classname" "light" +} +{ +"classname" "light" +"origin" "1240 -224 -8" +"light" "200" +} +{ +"classname" "func_wall" +"spawnflags" "3072" +"model" "*33" +} +{ +"classname" "func_wall" +"spawnflags" "3072" +"model" "*34" +} +{ +"classname" "func_wall" +"spawnflags" "3072" +"model" "*35" +} +{ +"classname" "func_wall" +"spawnflags" "3072" +"model" "*36" +} +{ +"classname" "func_wall" +"spawnflags" "3072" +"model" "*37" +} +{ +"classname" "monster_ogre" +"origin" "1992 -192 200" +"angle" "180" +"spawnflags" "256" +} +{ +"classname" "func_door_secret" +"angle" "90" +"spawnflags" "2" +"targetname" "t51" +"model" "*38" +} +{ +"classname" "trigger_multiple" +"target" "t51" +"model" "*39" +} +{ +"classname" "func_plat" +"model" "*40" +} +{ +"classname" "light" +"origin" "1480 56 -168" +"light" "160" +} +{ +"origin" "1432 280 -64" +"classname" "light" +"light" "160" +} +{ +"light" "160" +"classname" "light" +"origin" "1424 280 96" +} +{ +"light" "160" +"origin" "1472 232 -168" +"classname" "light" +} +{ +"classname" "monster_zombie" +"origin" "1592 -24 160" +"angle" "180" +} +{ +"classname" "monster_zombie" +"origin" "1432 -304 112" +"angle" "135" +"spawnflags" "256" +} +{ +"classname" "monster_zombie" +"origin" "1304 -288 112" +"angle" "90" +"spawnflags" "256" +} +{ +"classname" "monster_zombie" +"origin" "1576 -216 136" +"angle" "180" +"spawnflags" "768" +} +{ +"classname" "monster_zombie" +"origin" "1928 -80 200" +"spawnflags" "768" +"angle" "180" +} +{ +"angle" "180" +"spawnflags" "768" +"origin" "1928 -280 200" +"classname" "monster_zombie" +} +{ +"classname" "trigger_changelevel" +"map" "e2m3" +"model" "*41" +} +{ +"classname" "light" +"origin" "80 1544 40" +"light" "160" +} +{ +"light" "160" +"origin" "-112 1544 40" +"classname" "light" +} +{ +"classname" "item_shells" +"origin" "1056 552 8" +} +{ +"classname" "light" +"origin" "-112 1672 40" +"light" "160" +} +{ +"light" "160" +"origin" "88 1680 40" +"classname" "light" +} +{ +"classname" "item_health" +"origin" "-168 1688 -8" +"spawnflags" "1" +} +{ +"classname" "monster_knight" +"origin" "-112 1616 16" +"angle" "45" +"spawnflags" "768" +} +{ +"classname" "monster_demon1" +"origin" "1760 -208 -216" +"angle" "180" +} +{ +"classname" "trigger_secret" +"model" "*42" +} +{ +"classname" "trigger_secret" +"model" "*43" +} +{ +"classname" "trigger_secret" +"model" "*44" +} +{ +"classname" "trigger_multiple" +"target" "t29" +"model" "*45" +} +{ +"classname" "item_shells" +"origin" "-144 -1728 288" +"spawnflags" "768" +} +{ +"classname" "item_rockets" +"origin" "2000 -304 176" +"spawnflags" "1793" +} +{ +"classname" "item_rockets" +"origin" "2000 -112 176" +"spawnflags" "1793" +} +{ +"origin" "-62 -702 72" +"classname" "ambient_swamp1" +} +{ +"classname" "ambient_swamp2" +"origin" "386 -702 72" +} +{ +"origin" "-246 -470 -264" +"classname" "ambient_swamp2" +} +{ +"classname" "ambient_swamp1" +"origin" "554 -454 -264" +} +{ +"origin" "162 -430 -264" +"classname" "ambient_swamp1" +} +{ +"spawnflags" "1" +"origin" "-72 576 104" +"classname" "item_shells" +} +{ +"spawnflags" "2048" +"wait" "10" +"target" "t16" +"classname" "trigger_multiple" +"model" "*46" +} +{ +"targetname" "t16" +"sounds" "4" +"wait" "-1" +"angle" "-1" +"classname" "func_door" +"model" "*47" +} +{ +"origin" "-184 1480 168" +"classname" "light" +"light" "160" +} +{ +"classname" "light" +"origin" "-224 1592 168" +"light" "100" +} +{ +"target" "t52" +"classname" "trigger_once" +"model" "*48" +} +{ +"mangle" "20 30 0" +"origin" "1224 -288 336" +"classname" "info_intermission" +} +{ +"mangle" "20 180 0" +"origin" "-352 760 240" +"classname" "info_intermission" +} +{ +"mangle" "20 135 0" +"origin" "480 -440 208" +"classname" "info_intermission" +} +{ +"angle" "90" +"origin" "-176 -1904 264" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "-128 -1848 264" +"angle" "90" +} +{ +"angle" "90" +"origin" "-192 -1808 264" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "-320 -1824 264" +"angle" "90" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*49" +} +{ +"spawnflags" "1792" +"origin" "200 -664 0" +"classname" "weapon_lightning" +} +{ +"origin" "-184 1512 144" +"classname" "item_artifact_super_damage" +} +{ +"classname" "item_cells" +"origin" "240 -664 0" +"spawnflags" "1793" +} +{ +"classname" "item_cells" +"origin" "392 640 0" +"spawnflags" "1793" +} +{ +"classname" "item_cells" +"origin" "-168 456 104" +"spawnflags" "1793" +} +{ +"classname" "func_door" +"angle" "-1" +"spawnflags" "1" +"wait" "6" +"speed" "1000" +"sounds" "3" +"targetname" "t15" +"model" "*50" +} +{ +"classname" "weapon_grenadelauncher" +"origin" "1312 280 56" +"spawnflags" "3584" +} +{ +"sounds" "2" +"wait" "5" +"message" "Shoot the buttons..." +"spawnflags" "3584" +"classname" "trigger_multiple" +"targetname" "t53" +"model" "*51" +} +{ +"classname" "trigger_relay" +"origin" "-72 -320 48" +"targetname" "t13" +"killtarget" "t53" +} diff --git a/Misc/qs_pak/maps/e2m3.diff b/Misc/qs_pak/maps/e2m3.diff new file mode 100644 index 0000000..4d51967 --- /dev/null +++ b/Misc/qs_pak/maps/e2m3.diff @@ -0,0 +1,74 @@ +--- e2m3.ent ++++ e2m3@237a.ent +@@ -1226,21 +1226,23 @@ + { + "classname" "func_door" + "angle" "-2" + "spawnflags" "1" + "targetname" "t11" + "wait" "10" + "model" "*25" ++"lip" "7" // svdijk -- added to prevent z-fighting + } + { + "classname" "func_door_secret" + "angle" "90" + "spawnflags" "8" + "targetname" "t11" + "model" "*26" ++"t_length" "65" // svdijk -- added to prevent z-fighting + } + { + "classname" "light" + "origin" "1248 -288 312" + "light" "150" + } + { +@@ -1907,14 +1909,15 @@ + } + { + "classname" "func_door_secret" + "spawnflags" "2051" + "targetname" "t36" + "angle" "90" + "model" "*37" ++"t_length" "65" // svdijk -- added to prevent z-fighting + } + { + "classname" "light" + "origin" "-1288 640 -80" + "light" "160" + } + { +@@ -1939,14 +1942,15 @@ + } + { + "classname" "func_door_secret" + "targetname" "t36" + "angle" "270" + "spawnflags" "2049" + "model" "*38" ++"t_length" "65" // svdijk -- added to prevent z-fighting + } + { + "classname" "item_armor2" + "origin" "1128 600 -176" + "spawnflags" "1024" + } + { +@@ -2643,14 +2647,15 @@ + } + { + "classname" "func_door_secret" + "angle" "180" + "spawnflags" "2" + "targetname" "t41" + "model" "*66" ++"t_length" "65" // svdijk -- added to prevent z-fighting + } + { + "classname" "trigger_multiple" + "target" "t41" + "model" "*67" + } + { diff --git a/Misc/qs_pak/maps/e2m3@237a.ent b/Misc/qs_pak/maps/e2m3@237a.ent new file mode 100644 index 0000000..da83d17 --- /dev/null +++ b/Misc/qs_pak/maps/e2m3@237a.ent @@ -0,0 +1,2865 @@ +{ +"classname" "worldspawn" +"wad" "gfx/jr_med.wad" +"worldtype" "0" +"sounds" "9" +"message" "the Crypt of Decay" +} +{ +"classname" "light" +"origin" "192 -648 128" +} +{ +"classname" "info_player_start" +"origin" "688 -1600 -312" +"angle" "180" +} +{ +"classname" "light_flame_large_yellow" +"origin" "650 -438 4" +} +{ +"origin" "386 -438 4" +"classname" "light_flame_large_yellow" +} +{ +"origin" "66 -886 4" +"classname" "light_flame_large_yellow" +} +{ +"origin" "322 -886 4" +"classname" "light_flame_large_yellow" +} +{ +"light" "250" +"origin" "192 -1408 288" +"classname" "light" +} +{ +"light" "250" +"classname" "light" +"origin" "192 -1088 288" +} +{ +"light" "250" +"origin" "112 -1248 272" +"classname" "light" +} +{ +"light" "250" +"classname" "light" +"origin" "272 -1248 272" +} +{ +"light" "200" +"origin" "192 -1056 32" +"classname" "light" +} +{ +"light" "150" +"origin" "192 -1248 24" +"classname" "light" +} +{ +"origin" "194 -1462 108" +"classname" "light_flame_large_yellow" +} +{ +"origin" "194 -1030 164" +"classname" "light_torch_small_walltorch" +} +{ +"light" "150" +"origin" "192 -1440 32" +"classname" "light" +} +{ +"sounds" "2" +"classname" "func_plat" +"spawnflags" "1" +"model" "*1" +} +{ +"origin" "226 -1670 -212" +"classname" "light_flame_large_yellow" +} +{ +"light" "150" +"origin" "88 -1552 -184" +"classname" "light" +} +{ +"origin" "-22 -1374 -212" +"classname" "light_flame_large_yellow" +} +{ +"light" "150" +"origin" "328 -1256 -184" +"classname" "light" +} +{ +"light" "150" +"classname" "light" +"origin" "56 -1256 -184" +} +{ +"classname" "light" +"origin" "248 -1480 -184" +"light" "150" +} +{ +"light" "250" +"origin" "552 -1608 -72" +"classname" "light" +} +{ +"light" "150" +"origin" "432 -1656 -224" +"classname" "light" +} +{ +"light" "150" +"origin" "432 -1496 -224" +"classname" "light" +} +{ +"light" "100" +"origin" "192 -1248 -40" +"classname" "light" +} +{ +"origin" "10 -438 4" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light_flame_large_yellow" +"origin" "-254 -438 4" +} +{ +"light" "150" +"origin" "192 -704 -136" +"classname" "light" +} +{ +"light" "250" +"origin" "192 -512 -136" +"classname" "light" +} +{ +"classname" "light" +"origin" "416 -512 -136" +"light" "150" +} +{ +"light" "150" +"origin" "-32 -512 -136" +"classname" "light" +} +{ +"classname" "light" +"origin" "-208 -512 -136" +"light" "150" +} +{ +"light" "150" +"origin" "592 -512 -136" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 -840 -136" +"light" "150" +} +{ +"light" "150" +"origin" "-352 -672 -168" +"classname" "light" +} +{ +"classname" "light" +"origin" "-320 -832 -168" +"light" "150" +} +{ +"light" "150" +"origin" "-320 -512 -168" +"classname" "light" +} +{ +"classname" "light" +"origin" "696 -512 -168" +"light" "150" +} +{ +"light" "150" +"origin" "736 -672 -168" +"classname" "light" +} +{ +"classname" "light" +"origin" "704 -832 -168" +"light" "150" +} +{ +"light" "150" +"origin" "512 -864 -168" +"classname" "light" +} +{ +"classname" "light" +"origin" "-128 -864 -168" +"light" "150" +} +{ +"light" "200" +"origin" "-128 -320 8" +"classname" "light" +} +{ +"classname" "light" +"origin" "512 -320 8" +"light" "200" +} +{ +"origin" "384 -24 32" +"classname" "light" +} +{ +"classname" "light" +"origin" "0 -24 32" +} +{ +"light" "200" +"origin" "416 -192 -8" +"classname" "light" +} +{ +"classname" "light" +"origin" "-32 -192 -8" +"light" "200" +} +{ +"light" "200" +"origin" "840 48 72" +"classname" "light" +} +{ +"light" "150" +"origin" "576 -24 -56" +"classname" "light" +} +{ +"light" "200" +"origin" "624 -24 72" +"classname" "light" +} +{ +"origin" "1002 354 -60" +"classname" "light_flame_large_yellow" +} +{ +"light" "100" +"origin" "1000 352 -128" +"classname" "light" +} +{ +"classname" "light" +"origin" "736 8 72" +"light" "200" +} +{ +"light" "200" +"origin" "936 88 72" +"classname" "light" +} +{ +"light" "150" +"origin" "688 -8 -32" +"classname" "light" +} +{ +"classname" "light" +"origin" "784 24 -104" +"light" "150" +} +{ +"light" "150" +"origin" "888 72 -32" +"classname" "light" +} +{ +"light" "200" +"origin" "872 208 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "872 400 -56" +"light" "200" +} +{ +"light" "200" +"origin" "872 592 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "744 568 88" +"light" "150" +} +{ +"light" "150" +"origin" "744 648 88" +"classname" "light" +} +{ +"classname" "light" +"origin" "704 608 -80" +"light" "150" +} +{ +"origin" "866 730 -60" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light" +"origin" "864 728 -128" +"light" "100" +} +{ +"sounds" "3" +"wait" "-1" +"targetname" "t8" +"spawnflags" "2049" +"angle" "0" +"classname" "func_door" +"model" "*2" +} +{ +"spawnflags" "2048" +"angle" "90" +"target" "t8" +"classname" "func_button" +"wait" "-1" +"model" "*3" +} +{ +"origin" "520 608 -64" +"classname" "light" +} +{ +"light" "400" +"origin" "192 608 -24" +"classname" "light" +} +{ +"sounds" "1" +"wait" "-1" +"angle" "270" +"spawnflags" "2058" +"classname" "func_door_secret" +"targetname" "t9" +"model" "*4" +} +{ +"light" "150" +"origin" "1064 640 -112" +"classname" "light" +} +{ +"targetname" "t9" +"angle" "180" +"origin" "1024 640 -152" +"classname" "monster_zombie" +} +{ +"targetname" "t9" +"angle" "180" +"origin" "1120 672 -152" +"classname" "monster_zombie" +} +{ +"targetname" "t9" +"angle" "180" +"origin" "1088 600 -152" +"classname" "monster_zombie" +} +{ +"origin" "976 336 -176" +"classname" "item_health" +} +{ +"light" "150" +"origin" "192 608 -104" +"classname" "light" +} +{ +"origin" "192 288 -64" +"classname" "light" +} +{ +"classname" "light_flame_large_yellow" +"origin" "66 106 4" +"light" "200" +} +{ +"light" "200" +"origin" "-30 106 4" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light" +"origin" "504 120 -248" +"light" "200" +} +{ +"light" "200" +"origin" "704 224 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "704 472 -248" +"light" "200" +} +{ +"light" "200" +"origin" "304 112 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "80 112 -248" +"light" "200" +} +{ +"light" "200" +"origin" "720 608 -248" +"classname" "light" +} +{ +"spawnflags" "2048" +"sounds" "1" +"wait" "-1" +"targetname" "t3" +"classname" "func_door" +"angle" "90" +"model" "*5" +} +{ +"spawnflags" "2048" +"wait" "-1" +"angle" "270" +"classname" "func_door" +"message" "This door opens nearby..." +"model" "*6" +} +{ +"spawnflags" "2048" +"target" "t3" +"wait" "-1" +"classname" "func_button" +"angle" "180" +"model" "*7" +} +{ +"light" "250" +"origin" "-448 184 0" +"classname" "light" +} +{ +"light" "150" +"origin" "-552 280 -224" +"classname" "light" +} +{ +"classname" "light" +"origin" "-392 280 -224" +"light" "150" +} +{ +"light" "150" +"origin" "-416 -32 104" +"classname" "light" +} +{ +"classname" "light" +"origin" "-320 -32 104" +"light" "150" +} +{ +"light" "150" +"origin" "-224 -32 104" +"classname" "light" +} +{ +"light" "250" +"origin" "-352 -32 -32" +"classname" "light" +} +{ +"light" "200" +"origin" "-1160 88 -248" +"classname" "light" +} +{ +"light" "200" +"origin" "-1048 88 -32" +"classname" "light" +} +{ +"light" "200" +"classname" "light" +"origin" "-1160 224 -32" +} +{ +"origin" "-742 658 -44" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light_flame_large_yellow" +"origin" "-926 658 -44" +} +{ +"light" "200" +"origin" "-736 632 -96" +"classname" "light" +} +{ +"classname" "light" +"origin" "-928 632 -96" +"light" "200" +} +{ +"light" "150" +"origin" "-600 104 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "-696 424 -248" +"light" "150" +} +{ +"light" "150" +"origin" "-1152 408 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "-944 432 -248" +"light" "150" +} +{ +"light" "150" +"origin" "-856 64 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "-160 152 -248" +"light" "150" +} +{ +"light" "150" +"origin" "-160 448 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "-328 464 -248" +"light" "150" +} +{ +"light" "150" +"origin" "-256 176 -248" +"classname" "light" +} +{ +"origin" "-574 410 -172" +"classname" "light_flame_large_yellow" +} +{ +"classname" "light_flame_large_yellow" +"origin" "-470 410 -172" +} +{ +"target" "t4" +"classname" "trigger_teleport" +"model" "*8" +} +{ +"light" "200" +"style" "2" +"origin" "-760 576 -216" +"classname" "light" +} +{ +"targetname" "t4" +"angle" "180" +"origin" "120 -32 -112" +"classname" "info_teleport_destination" +} +{ +"light" "200" +"origin" "-264 384 112" +"classname" "light" +} +{ +"classname" "light" +"origin" "-264 288 112" +"light" "200" +} +{ +"light" "200" +"origin" "-264 192 112" +"classname" "light" +} +{ +"classname" "light" +"origin" "-264 480 112" +"light" "200" +} +{ +"light" "250" +"origin" "-264 304 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "-520 424 0" +"light" "250" +} +{ +"light" "200" +"origin" "-1120 608 72" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1024 584 72" +"light" "200" +} +{ +"light" "200" +"origin" "-928 584 72" +"classname" "light" +} +{ +"classname" "light" +"origin" "-832 584 72" +"light" "200" +} +{ +"light" "200" +"origin" "-544 584 72" +"classname" "light" +} +{ +"classname" "light" +"origin" "-640 584 72" +"light" "200" +} +{ +"light" "150" +"origin" "-480 768 56" +"classname" "light" +} +{ +"classname" "light" +"origin" "-384 768 56" +"light" "150" +} +{ +"classname" "light" +"origin" "-712 120 -32" +"light" "200" +} +{ +"sounds" "3" +"wait" "-1" +"targetname" "t5" +"spawnflags" "2049" +"angle" "180" +"classname" "func_door" +"model" "*9" +} +{ +"spawnflags" "2048" +"angle" "270" +"target" "t5" +"wait" "-1" +"classname" "func_button" +"model" "*10" +} +{ +"style" "32" +"targetname" "t5" +"light" "200" +"origin" "-352 552 -56" +"classname" "light" +} +{ +"light" "150" +"origin" "-432 768 -56" +"classname" "light" +} +{ +"light" "150" +"origin" "-520 680 -56" +"classname" "light" +} +{ +"spawnflags" "2048" +"sounds" "3" +"targetname" "t5" +"wait" "-1" +"angle" "-2" +"classname" "func_door" +"model" "*11" +} +{ +"sounds" "3" +"targetname" "t5" +"spawnflags" "2049" +"wait" "-1" +"angle" "90" +"classname" "func_door" +"model" "*12" +} +{ +"origin" "-72 848 -56" +"classname" "light" +} +{ +"origin" "-120 600 -8" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 904 -8" +} +{ +"light" "200" +"origin" "192 888 -248" +"classname" "light" +} +{ +"classname" "light" +"origin" "-104 600 -248" +"light" "200" +} +{ +"light" "200" +"origin" "376 984 -120" +"classname" "light" +} +{ +"classname" "light" +"origin" "504 760 -120" +"light" "200" +} +{ +"light" "200" +"origin" "-32 608 200" +"classname" "light" +} +{ +"spawnflags" "2048" +"sounds" "3" +"wait" "-1" +"angle" "-2" +"classname" "func_door" +"model" "*13" +} +{ +"origin" "-16 1456 16" +"classname" "light" +} +{ +"light" "200" +"origin" "384 1248 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "384 1440 -56" +"light" "200" +} +{ +"light" "150" +"origin" "256 1440 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 1248 -56" +"light" "200" +} +{ +"light" "200" +"origin" "384 1344 -88" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 1152 -88" +"light" "200" +} +{ +"classname" "light" +"origin" "8 1456 -120" +"light" "200" +} +{ +"spawnflags" "2048" +"sounds" "1" +"classname" "item_key2" +"origin" "-16 1456 -152" +} +{ +"classname" "func_door" +"angle" "-1" +"targetname" "t6" +"speed" "400" +"wait" "-1" +"sounds" "4" +"model" "*14" +} +{ +"classname" "func_door" +"angle" "-1" +"targetname" "t6" +"speed" "400" +"wait" "-1" +"sounds" "4" +"model" "*15" +} +{ +"classname" "func_door" +"angle" "-1" +"targetname" "t6" +"speed" "400" +"wait" "-1" +"sounds" "4" +"spawnflags" "2048" +"model" "*16" +} +{ +"classname" "trigger_once" +"target" "t6" +"model" "*17" +} +{ +"classname" "light" +"origin" "-192 1456 -136" +"light" "80" +} +{ +"classname" "light" +"origin" "-16 1280 -136" +"light" "80" +} +{ +"spawnflags" "768" +"classname" "monster_hell_knight" +"origin" "-16 1280 -168" +"angle" "90" +"targetname" "t6" +} +{ +"spawnflags" "256" +"angle" "270" +"origin" "-16 1632 -168" +"classname" "monster_hell_knight" +"targetname" "t6" +} +{ +"classname" "monster_hell_knight" +"origin" "-192 1456 -168" +"angle" "0" +"targetname" "t6" +} +{ +"classname" "light" +"origin" "152 1440 -56" +"light" "150" +} +{ +"classname" "item_shells" +"origin" "-104 1512 -192" +"spawnflags" "1" +} +{ +"wait" "-1" +"classname" "func_door" +"angle" "0" +"spawnflags" "2056" +"model" "*18" +} +{ +"wait" "-1" +"classname" "func_door" +"angle" "180" +"spawnflags" "2056" +"model" "*19" +} +{ +"classname" "light" +"origin" "-1120 832 -48" +"light" "250" +} +{ +"classname" "light" +"origin" "-1120 976 -24" +"light" "250" +} +{ +"classname" "light" +"origin" "-1240 1296 216" +"light" "200" +} +{ +"light" "200" +"origin" "-1240 1416 216" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1240 1176 216" +"light" "200" +} +{ +"classname" "light" +"origin" "-712 1416 216" +"light" "200" +} +{ +"light" "200" +"origin" "-712 1296 216" +"classname" "light" +} +{ +"classname" "light" +"origin" "-712 1176 216" +"light" "200" +} +{ +"light" "200" +"origin" "-856 1296 216" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1096 1296 216" +"light" "200" +} +{ +"light" "200" +"origin" "-976 1296 216" +"classname" "light" +} +{ +"classname" "light_flame_small_white" +"origin" "-1318 1514 -8" +} +{ +"origin" "-1318 1514 64" +"classname" "light_flame_small_white" +} +{ +"classname" "light_flame_small_white" +"origin" "-1318 1514 144" +} +{ +"origin" "-634 1078 -8" +"classname" "light_flame_small_white" +} +{ +"classname" "light_flame_small_white" +"origin" "-634 1078 64" +} +{ +"origin" "-634 1078 144" +"classname" "light_flame_small_white" +} +{ +"classname" "func_plat" +"spawnflags" "1" +"model" "*20" +} +{ +"classname" "light" +"origin" "-320 1536 184" +"light" "250" +} +{ +"classname" "light" +"origin" "-376 1312 120" +"light" "250" +} +{ +"classname" "light" +"origin" "-16 1536 184" +"light" "200" +} +{ +"light" "200" +"origin" "-560 1312 56" +"classname" "light" +} +{ +"classname" "light" +"origin" "24 1120 216" +"light" "150" +} +{ +"classname" "light" +"origin" "192 608 176" +"light" "200" +} +{ +"wait" "-1" +"classname" "func_door" +"angle" "90" +"spawnflags" "2049" +"targetname" "t7" +"sounds" "1" +"model" "*21" +} +{ +"light" "150" +"origin" "400 1104 224" +"classname" "light" +} +{ +"light" "150" +"origin" "-200 1056 224" +"classname" "light" +} +{ +"light" "200" +"origin" "192 960 224" +"classname" "light" +} +{ +"light" "200" +"origin" "192 784 224" +"classname" "light" +} +{ +"classname" "light" +"origin" "672 264 184" +"light" "200" +} +{ +"light" "200" +"origin" "384 184 184" +"classname" "light" +} +{ +"classname" "light" +"origin" "512 256 8" +"light" "200" +} +{ +"classname" "light" +"origin" "8 184 200" +"light" "200" +} +{ +"classname" "light" +"origin" "584 744 200" +"light" "200" +} +{ +"classname" "light" +"origin" "0 432 200" +"light" "200" +} +{ +"classname" "trigger_once" +"targetname" "t8" +"target" "t9" +"delay" "10" +"model" "*22" +} +{ +"classname" "light" +"origin" "192 -72 216" +"light" "250" +} +{ +"classname" "light" +"origin" "360 -168 360" +"light" "200" +} +{ +"classname" "light" +"origin" "296 -168 360" +"light" "200" +} +{ +"origin" "472 -168 368" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"classname" "light" +"origin" "408 -168 360" +} +{ +"classname" "light" +"origin" "384 -168 248" +"light" "200" +} +{ +"classname" "light" +"origin" "936 -304 328" +"light" "200" +} +{ +"light" "200" +"origin" "1000 -232 360" +"classname" "light" +} +{ +"classname" "light" +"origin" "864 -232 360" +"light" "200" +} +{ +"light" "200" +"origin" "864 -368 360" +"classname" "light" +} +{ +"classname" "light" +"origin" "1000 -368 360" +"light" "200" +} +{ +"classname" "light" +"origin" "736 -248 280" +"light" "200" +} +{ +"light" "200" +"origin" "552 -216 280" +"classname" "light" +} +{ +"classname" "light" +"origin" "16 144 -32" +"light" "200" +} +{ +"classname" "light" +"origin" "0 432 -136" +"light" "200" +} +{ +"classname" "light" +"origin" "192 384 184" +"light" "200" +} +{ +"light" "200" +"origin" "192 192 184" +"classname" "light" +} +{ +"spawnflags" "2048" +"classname" "func_button" +"wait" "-1" +"target" "t7" +"model" "*23" +} +{ +"style" "33" +"targetname" "t7" +"classname" "light" +"origin" "176 1152 200" +"target" "t10" +} +{ +"classname" "info_null" +"origin" "292 1152 180" +"targetname" "t10" +} +{ +"classname" "light" +"origin" "192 1224 200" +"light" "150" +} +{ +"light" "150" +"origin" "192 1376 200" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 1536 200" +"light" "200" +} +{ +"light" "150" +"origin" "192 1080 200" +"classname" "light" +} +{ +"classname" "weapon_nailgun" +"origin" "184 -1520 -272" +} +{ +"classname" "func_button" +"target" "t11" +"angle" "-1" +"targetname" "t12" +"lip" "4" +"wait" "0.1" +"speed" "300" +"health" "1" +"model" "*24" +} +{ +"classname" "func_door" +"angle" "-2" +"spawnflags" "1" +"targetname" "t11" +"wait" "10" +"model" "*25" +"lip" "7" // svdijk -- added to prevent z-fighting +} +{ +"classname" "func_door_secret" +"angle" "90" +"spawnflags" "8" +"targetname" "t11" +"model" "*26" +"t_length" "65" // svdijk -- added to prevent z-fighting +} +{ +"classname" "light" +"origin" "1248 -288 312" +"light" "150" +} +{ +"light" "200" +"origin" "1176 -400 352" +"classname" "light" +} +{ +"classname" "item_health" +"origin" "1336 -536 256" +"spawnflags" "2" +} +{ +"classname" "light" +"origin" "1320 -488 352" +"light" "200" +} +{ +"classname" "trigger_multiple" +"target" "t11" +"wait" "10" +"model" "*27" +} +{ +"classname" "item_armor1" +"origin" "192 -592 -64" +} +{ +"classname" "item_shells" +"origin" "176 592 -160" +"spawnflags" "1" +} +{ +"classname" "weapon_nailgun" +"origin" "-80 1456 -192" +"spawnflags" "1792" +} +{ +"classname" "weapon_rocketlauncher" +"origin" "56 1144 128" +"spawnflags" "1792" +} +{ +"classname" "weapon_grenadelauncher" +"origin" "-736 608 -280" +"spawnflags" "1792" +} +{ +"classname" "item_spikes" +"origin" "1120 -384 256" +"spawnflags" "1" +} +{ +"classname" "item_rockets" +"origin" "840 -432 192" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "-456 312 -80" +} +{ +"classname" "monster_zombie" +"origin" "-1152 96 -88" +"spawnflags" "256" +"target" "t36" +} +{ +"origin" "-1120 32 -88" +"classname" "monster_zombie" +"spawnflags" "768" +"target" "t36" +} +{ +"classname" "monster_zombie" +"origin" "-1192 168 -88" +"target" "t36" +} +{ +"classname" "monster_shambler" +"origin" "-1120 1104 -56" +"angle" "270" +} +{ +"classname" "monster_hell_knight" +"origin" "-336 1312 8" +"angle" "180" +} +{ +"classname" "monster_ogre" +"origin" "-698 1446 -56" +"angle" "270" +"spawnflags" "256" +} +{ +"classname" "monster_hell_knight" +"origin" "-888 1128 8" +"angle" "90" +"spawnflags" "256" +} +{ +"classname" "item_health" +"origin" "-336 784 -128" +} +{ +"origin" "-408 608 -128" +"classname" "item_health" +} +{ +"classname" "path_corner" +"origin" "-1096 584 -120" +"targetname" "t13" +"target" "t14" +} +{ +"origin" "-496 584 -120" +"classname" "path_corner" +"targetname" "t14" +"target" "t13" +} +{ +"classname" "monster_demon1" +"origin" "-712 576 -104" +"angle" "180" +"target" "t13" +} +{ +"classname" "path_corner" +"origin" "-528 472 -96" +"targetname" "t15" +"target" "t16" +} +{ +"origin" "-368 -32 -96" +"classname" "path_corner" +"target" "t15" +"targetname" "t16" +} +{ +"classname" "monster_ogre" +"origin" "-466 262 -56" +"target" "t16" +"spawnflags" "256" +} +{ +"target" "t22" +"targetname" "t21" +"origin" "56 -184 -120" +"classname" "path_corner" +} +{ +"target" "t21" +"targetname" "t20" +"classname" "path_corner" +"origin" "-128 -224 -120" +} +{ +"target" "t20" +"targetname" "t19" +"origin" "-128 -504 -56" +"classname" "path_corner" +} +{ +"target" "t19" +"targetname" "t18" +"classname" "path_corner" +"origin" "512 -504 -56" +} +{ +"target" "t18" +"targetname" "t17" +"origin" "512 -224 -120" +"classname" "path_corner" +} +{ +"targetname" "t26" +"target" "t17" +"classname" "path_corner" +"origin" "328 -184 -120" +} +{ +"target" "t23" +"targetname" "t22" +"classname" "path_corner" +"origin" "-128 -200 -120" +} +{ +"target" "t24" +"targetname" "t23" +"classname" "path_corner" +"origin" "-128 -552 -56" +} +{ +"target" "t25" +"targetname" "t24" +"origin" "512 -552 -56" +"classname" "path_corner" +} +{ +"target" "t26" +"targetname" "t25" +"classname" "path_corner" +"origin" "512 -200 -120" +} +{ +"spawnflags" "256" +"target" "t21" +"origin" "0 -184 -104" +"classname" "monster_hell_knight" +} +{ +"spawnflags" "1" +"origin" "376 -160 -104" +"classname" "monster_hell_knight" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "190 -706 -40" +"classname" "monster_ogre" +} +{ +"spawnflags" "768" +"angle" "90" +"origin" "192 -1408 24" +"classname" "monster_hell_knight" +} +{ +"origin" "40 -1424 0" +"classname" "item_shells" +"spawnflags" "1" +} +{ +"spawnflags" "1" +"origin" "304 -1096 0" +"classname" "item_health" +} +{ +"origin" "328 -1280 -272" +"classname" "item_health" +} +{ +"origin" "40 -1256 -272" +"classname" "item_spikes" +} +{ +"target" "t28" +"targetname" "t27" +"origin" "864 176 -168" +"classname" "path_corner" +} +{ +"target" "t27" +"targetname" "t28" +"classname" "path_corner" +"origin" "864 616 -168" +} +{ +"target" "t27" +"origin" "862 446 -152" +"classname" "monster_ogre" +} +{ +"spawnflags" "768" +"angle" "180" +"origin" "526 -26 -104" +"classname" "monster_ogre" +} +{ +"spawnflags" "256" +"angle" "0" +"origin" "-72 -24 -104" +"classname" "monster_hell_knight" +} +{ +"spawnflags" "256" +"origin" "-274 -34 -104" +"classname" "monster_ogre" +} +{ +"spawnflags" "768" +"angle" "270" +"origin" "-512 760 -104" +"classname" "monster_hell_knight" +} +{ +"spawnflags" "1793" +"origin" "336 1104 128" +"classname" "item_rockets" +} +{ +"angle" "45" +"spawnflags" "256" +"origin" "104 256 -136" +"classname" "monster_zombie" +} +{ +"spawnflags" "769" +"angle" "90" +"origin" "192 488 -136" +"classname" "monster_hell_knight" +} +{ +"wait" "1" +"speed" "250" +"lip" "16" +"spawnflags" "5" +"targetname" "t29" +"dmg" "20" +"angle" "180" +"classname" "func_door" +"model" "*28" +} +{ +"wait" "1" +"targetname" "t29" +"speed" "250" +"dmg" "20" +"lip" "16" +"spawnflags" "5" +"classname" "func_door" +"sounds" "1" +"model" "*29" +} +{ +"wait" "2" +"target" "t29" +"classname" "trigger_multiple" +"model" "*30" +} +{ +"origin" "506 1762 -124" +"classname" "light_torch_small_walltorch" +"style" "1" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "274 2010 -124" +} +{ +"light" "200" +"origin" "152 1824 -40" +"classname" "light" +} +{ +"classname" "light" +"origin" "288 1824 -40" +"light" "200" +} +{ +"light" "200" +"origin" "288 1696 -40" +"classname" "light" +} +{ +"classname" "light" +"origin" "152 1704 -40" +"light" "200" +} +{ +"light" "220" +"origin" "0 1704 -40" +"classname" "light" +} +{ +"angle" "180" +"classname" "func_door_secret" +"targetname" "t35" +"spawnflags" "16" +"sounds" "1" +"model" "*31" +} +{ +"spawnflags" "2" +"origin" "-16 1816 -192" +"classname" "item_health" +} +{ +"origin" "432 1672 -320" +"classname" "light" +"light" "220" +} +{ +"spawnflags" "1792" +"origin" "-16 1752 -192" +"classname" "item_rockets" +} +{ +"angle" "270" +"origin" "-192 1580 168" +"classname" "trap_spikeshooter" +"targetname" "t32" +} +{ +"spawnflags" "256" +"classname" "trap_spikeshooter" +"origin" "236 1536 168" +"angle" "180" +"targetname" "t32" +} +{ +"classname" "trap_spikeshooter" +"origin" "0 1580 168" +"angle" "270" +"targetname" "t32" +} +{ +"angle" "90" +"origin" "-96 1492 168" +"classname" "trap_spikeshooter" +"targetname" "t32" +} +{ +"classname" "trap_spikeshooter" +"origin" "148 1376 168" +"angle" "0" +"targetname" "t33" +} +{ +"angle" "180" +"origin" "236 1256 168" +"classname" "trap_spikeshooter" +"targetname" "t33" +} +{ +"spawnflags" "256" +"classname" "trap_spikeshooter" +"origin" "192 1580 168" +"angle" "270" +"targetname" "t33" +} +{ +"spawnflags" "257" +"angle" "90" +"origin" "192 -16 153" +"classname" "monster_hell_knight" +} +{ +"spawnflags" "257" +"angle" "180" +"origin" "864 -248 217" +"classname" "monster_shambler" +} +{ +"angle" "180" +"origin" "464 -184 185" +"classname" "monster_hell_knight" +} +{ +"classname" "monster_hell_knight" +"origin" "192 -176 153" +"angle" "90" +"spawnflags" "1" +"target" "t31" +} +{ +"spawnflags" "769" +"angle" "90" +"origin" "190 1166 153" +"classname" "monster_ogre" +} +{ +"spawnflags" "2048" +"origin" "48 1456 -192" +"classname" "weapon_grenadelauncher" +} +{ +"spawnflags" "1" +"origin" "-96 1376 -192" +"classname" "item_rockets" +} +{ +"angle" "270" +"spawnflags" "768" +"classname" "monster_ogre" +"origin" "862 662 -152" +} +{ +"light" "120" +"origin" "192 -352 -264" +"classname" "light" +} +{ +"spawnflags" "768" +"origin" "326 -1490 -248" +"classname" "monster_ogre" +"angle" "180" +"target" "t42" +} +{ +"origin" "192 552 128" +"classname" "item_health" +} +{ +"spawnflags" "1024" +"classname" "item_health" +"origin" "176 672 128" +} +{ +"spawnflags" "1025" +"origin" "184 1312 128" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "-16 1480 128" +"spawnflags" "1025" +} +{ +"origin" "672 -328 176" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "776 -192 176" +} +{ +"origin" "784 312 -176" +"classname" "item_shells" +} +{ +"spawnflags" "256" +"angle" "315" +"origin" "-26 1094 152" +"classname" "monster_ogre" +} +{ +"classname" "monster_ogre" +"origin" "406 1094 152" +"angle" "225" +"spawnflags" "768" +} +{ +"spawnflags" "1" +"origin" "-200 1128 128" +"classname" "item_rockets" +} +{ +"origin" "-550 -478 212" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "-550 -166 212" +} +{ +"origin" "-214 -326 252" +"classname" "light_torch_small_walltorch" +} +{ +"light" "150" +"origin" "-120 -176 192" +"classname" "light" +} +{ +"classname" "light" +"origin" "56 -176 192" +"light" "150" +} +{ +"origin" "-816 1488 -80" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "-752 1488 -80" +} +{ +"spawnflags" "1536" +"origin" "-688 1488 -80" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "176 1264 -160" +"classname" "item_shells" +} +{ +"classname" "func_door" +"angle" "270" +"targetname" "t31" +"wait" "-1" +"spawnflags" "2048" +"model" "*32" +} +{ +"wait" "-1" +"sounds" "1" +"speed" "300" +"classname" "func_door" +"angle" "90" +"model" "*33" +} +{ +"speed" "300" +"classname" "func_door" +"angle" "270" +"wait" "-1" +"model" "*34" +} +{ +"classname" "item_health" +"origin" "400 1464 -160" +"spawnflags" "1024" +} +{ +"classname" "item_health" +"origin" "-1136 528 -128" +} +{ +"classname" "monster_ogre" +"origin" "-370 -218 88" +"targetname" "t31" +"spawnflags" "1" +} +{ +"classname" "monster_hell_knight" +"origin" "-64 -176 152" +"angle" "0" +"spawnflags" "768" +"targetname" "t31" +} +{ +"classname" "item_health" +"origin" "-488 -480 64" +} +{ +"classname" "light" +"origin" "680 -1600 -160" +} +{ +"classname" "item_spikes" +"origin" "984 -192 192" +"spawnflags" "1" +} +{ +"classname" "monster_ogre" +"origin" "-490 -410 88" +"angle" "45" +} +{ +"classname" "trigger_multiple" +"target" "t32" +"wait" "1" +"spawnflags" "1024" +"targetname" "t44" +"model" "*35" +} +{ +"classname" "trigger_multiple" +"target" "t33" +"wait" "1" +"spawnflags" "1024" +"model" "*36" +} +{ +"classname" "item_rockets" +"origin" "-96 272 -384" +} +{ +"classname" "weapon_supernailgun" +"origin" "-1200 208 -112" +"spawnflags" "1792" +} +{ +"classname" "func_door_secret" +"spawnflags" "2051" +"targetname" "t36" +"angle" "90" +"model" "*37" +"t_length" "65" // svdijk -- added to prevent z-fighting +} +{ +"classname" "light" +"origin" "-1288 640 -80" +"light" "160" +} +{ +"light" "160" +"origin" "-1288 128 -80" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1288 264 -80" +"light" "160" +} +{ +"light" "160" +"origin" "-1288 392 -80" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1288 520 -80" +"light" "160" +} +{ +"classname" "func_door_secret" +"targetname" "t36" +"angle" "270" +"spawnflags" "2049" +"model" "*38" +"t_length" "65" // svdijk -- added to prevent z-fighting +} +{ +"classname" "item_armor2" +"origin" "1128 600 -176" +"spawnflags" "1024" +} +{ +"classname" "func_door_secret" +"angle" "90" +"spawnflags" "11" +"targetname" "t34" +"model" "*39" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "-246 -1310 -204" +"light" "250" +} +{ +"classname" "trigger_once" +"target" "t34" +"model" "*40" +} +{ +"classname" "item_spikes" +"origin" "-240 -1288 -312" +"spawnflags" "1" +} +{ +"spawnflags" "1" +"origin" "-240 -1368 -312" +"classname" "item_spikes" +} +{ +"sounds" "3" +"classname" "func_door" +"angle" "-2" +"spawnflags" "3585" +"wait" "90" +"targetname" "t6" +"model" "*41" +} +{ +"sounds" "3" +"classname" "func_door" +"targetname" "t6" +"spawnflags" "3585" +"angle" "-2" +"wait" "90" +"model" "*42" +} +{ +"sounds" "3" +"classname" "func_door" +"angle" "0" +"spawnflags" "1537" +"targetname" "t6" +"wait" "90" +"model" "*43" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "-42 1642 -108" +"light" "200" +} +{ +"classname" "item_health" +"origin" "-72 560 -384" +} +{ +"origin" "-152 560 -384" +"classname" "item_health" +} +{ +"targetname" "t31" +"spawnflags" "513" +"angle" "90" +"origin" "848 -376 216" +"classname" "monster_hell_knight" +} +{ +"targetname" "t31" +"spawnflags" "768" +"origin" "-512 -248 88" +"classname" "monster_shambler" +} +{ +"spawnflags" "768" +"angle" "180" +"origin" "382 1246 -136" +"classname" "monster_ogre" +} +{ +"spawnflags" "768" +"classname" "monster_ogre" +"origin" "190 1438 -136" +"angle" "0" +} +{ +"classname" "info_player_deathmatch" +"origin" "640 -1664 -312" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "-400 -240 88" +"angle" "0" +} +{ +"classname" "info_player_deathmatch" +"origin" "-216 1088 152" +"angle" "0" +} +{ +"classname" "info_player_deathmatch" +"origin" "-512 792 -104" +"angle" "270" +} +{ +"classname" "info_player_deathmatch" +"origin" "888 624 -152" +"angle" "270" +} +{ +"classname" "info_player_deathmatch" +"origin" "-96 1456 -168" +"angle" "0" +} +{ +"spawnflags" "2048" +"classname" "func_wall" +"model" "*44" +} +{ +"spawnflags" "2048" +"classname" "func_wall" +"model" "*45" +} +{ +"classname" "func_plat" +"height" "192" +"sounds" "2" +"model" "*46" +} +{ +"classname" "item_health" +"origin" "-1184 72 -352" +} +{ +"classname" "light" +"origin" "-952 1408 80" +"light" "200" +} +{ +"classname" "light" +"origin" "-1120 1176 48" +"light" "250" +} +{ +"classname" "light" +"origin" "-104 288 -344" +"light" "200" +} +{ +"classname" "light" +"origin" "-512 376 -304" +"light" "200" +} +{ +"classname" "trigger_multiple" +"target" "t35" +"model" "*47" +} +{ +"classname" "light" +"origin" "192 -376 -16" +"light" "150" +} +{ +"classname" "light" +"origin" "192 -328 176" +"light" "150" +} +{ +"classname" "trigger_multiple" +"target" "t32" +"spawnflags" "768" +"wait" "0.5" +"model" "*48" +} +{ +"classname" "trigger_multiple" +"spawnflags" "768" +"wait" "0.5" +"target" "t33" +"targetname" "t44" +"model" "*49" +} +{ +"origin" "-1176 112 -112" +"classname" "item_rockets" +} +{ +"origin" "-736 544 -280" +"classname" "item_armorInv" +} +{ +"classname" "func_button" +"angle" "180" +"target" "t36" +"model" "*50" +} +{ +"classname" "info_null" +"origin" "-1332 1116 -36" +"targetname" "t37" +} +{ +"classname" "light" +"origin" "-1296 1120 -32" +"target" "t37" +"angle" "60" +} +{ +"spawnflags" "2048" +"classname" "func_wall" +"model" "*51" +} +{ +"spawnflags" "2048" +"classname" "func_wall" +"model" "*52" +} +{ +"classname" "light" +"origin" "192 -152 -344" +"light" "160" +} +{ +"classname" "light" +"origin" "192 32 -344" +"light" "160" +} +{ +"classname" "light" +"origin" "32 168 -344" +"light" "140" +} +{ +"light" "160" +"origin" "-16 408 -344" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 368 -344" +"light" "140" +} +{ +"light" "140" +"origin" "448 368 -344" +"classname" "light" +} +{ +"classname" "light" +"origin" "528 592 -344" +"light" "200" +} +{ +"light" "200" +"origin" "408 808 -344" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 832 -344" +"light" "200" +} +{ +"light" "200" +"origin" "-40 728 -344" +"classname" "light" +} +{ +"classname" "light" +"origin" "-168 632 -344" +"light" "200" +} +{ +"light" "200" +"origin" "-160 976 -344" +"classname" "light" +} +{ +"classname" "light" +"origin" "592 456 -344" +"light" "200" +} +{ +"classname" "light" +"origin" "192 352 -232" +"light" "200" +} +{ +"light" "200" +"origin" "32 264 -232" +"classname" "light" +} +{ +"classname" "light" +"origin" "-184 432 -336" +"light" "160" +} +{ +"light" "160" +"origin" "-192 144 -336" +"classname" "light" +} +{ +"classname" "light" +"origin" "-776 280 -312" +"light" "200" +} +{ +"light" "200" +"origin" "-864 432 -312" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1096 432 -312" +"light" "200" +} +{ +"light" "200" +"origin" "-1168 272 -312" +"classname" "light" +} +{ +"classname" "light" +"origin" "-944 64 -312" +"light" "200" +} +{ +"light" "200" +"origin" "-664 136 -312" +"classname" "light" +} +{ +"light" "200" +"origin" "-1112 592 -96" +"classname" "light" +} +{ +"classname" "light" +"origin" "-1040 1256 -32" +"light" "150" +} +{ +"light" "200" +"origin" "-880 1128 80" +"classname" "light" +} +{ +"classname" "light" +"origin" "-712 1400 -16" +"light" "200" +} +{ +"classname" "light" +"origin" "104 -600 -224" +"light" "200" +} +{ +"light" "200" +"origin" "280 -600 -224" +"classname" "light" +} +{ +"classname" "trigger_changelevel" +"map" "e2m4" +"model" "*53" +} +{ +"light" "160" +"origin" "-312 808 -72" +"classname" "light" +} +{ +"light" "200" +"origin" "192 1776 -312" +"classname" "light" +} +{ +"classname" "light" +"origin" "-392 568 -56" +"light" "160" +} +{ +"classname" "light" +"origin" "-1224 1504 8" +"light" "170" +} +{ +"light" "170" +"origin" "-1304 1392 8" +"classname" "light" +} +{ +"classname" "light" +"origin" "-640 1168 -8" +"light" "170" +} +{ +"light" "160" +"origin" "352 -1248 56" +"classname" "light" +} +{ +"classname" "light" +"origin" "40 -1248 56" +"light" "160" +} +{ +"light" "160" +"origin" "192 -1216 -176" +"classname" "light" +} +{ +"classname" "light" +"origin" "24 -1376 -232" +"light" "160" +} +{ +"light" "160" +"origin" "224 -1624 -232" +"classname" "light" +} +{ +"classname" "light" +"origin" "368 -1392 -232" +"light" "160" +} +{ +"classname" "light" +"origin" "8 -464 -40" +"light" "140" +} +{ +"light" "140" +"origin" "384 -464 -40" +"classname" "light" +} +{ +"classname" "light" +"origin" "-544 800 -56" +"light" "140" +} +{ +"classname" "trigger_secret" +"model" "*54" +} +{ +"classname" "trigger_secret" +"model" "*55" +} +{ +"light" "200" +"origin" "760 1856 -40" +"classname" "light" +} +{ +"classname" "light" +"origin" "760 1664 -40" +"light" "200" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "538 1762 -124" +"style" "1" +"light" "200" +} +{ +"style" "1" +"classname" "light_torch_small_walltorch" +"origin" "850 1930 -124" +"light" "200" +} +{ +"origin" "850 1618 -124" +"classname" "light_torch_small_walltorch" +"style" "1" +"light" "200" +} +{ +"classname" "light" +"origin" "912 1856 -40" +"light" "200" +} +{ +"light" "200" +"origin" "912 1664 -40" +"classname" "light" +} +{ +"light" "200" +"origin" "1064 1776 -172" +"classname" "light" +} +{ +"light" "200" +"origin" "1080 1856 -40" +"classname" "light" +} +{ +"classname" "light" +"origin" "1080 1664 -40" +"light" "200" +} +{ +"classname" "light" +"origin" "1176 1776 -172" +"light" "200" +} +{ +"light" "170" +"origin" "672 1768 -296" +"classname" "light" +} +{ +"target" "t38" +"classname" "trigger_teleport" +"model" "*56" +} +{ +"targetname" "t38" +"origin" "1144 1776 -88" +"classname" "info_teleport_destination" +} +{ +"map" "e2m7" +"classname" "trigger_changelevel" +"model" "*57" +} +{ +"light" "160" +"origin" "840 1768 -200" +"classname" "light" +} +{ +"light" "140" +"origin" "408 608 -344" +"classname" "light" +} +{ +"classname" "item_spikes" +"origin" "-16 240 -160" +"spawnflags" "2048" +} +{ +"classname" "weapon_supernailgun" +"origin" "-1256 1448 -80" +} +{ +"origin" "432 1160 152" +"classname" "item_artifact_super_damage" +} +{ +"message" "The portal lies beyond..." +"targetname" "t40" +"wait" "-1" +"speed" "20" +"sounds" "4" +"angle" "-2" +"classname" "func_door" +"model" "*58" +} +{ +"origin" "432 1672 -368" +"classname" "item_armor2" +} +{ +"target" "t39" +"sounds" "1" +"wait" "-1" +"classname" "func_button" +"model" "*59" +} +{ +"message" "The underwater barrier is lowered..." +"target" "t40" +"targetname" "t39" +"spawnflags" "1" +"classname" "trigger_once" +"model" "*60" +} +{ +"classname" "trigger_secret" +"model" "*61" +} +{ +"light" "200" +"origin" "-128 -704 -224" +"classname" "light" +} +{ +"classname" "light" +"origin" "512 -704 -224" +"light" "200" +} +{ +"light" "200" +"origin" "192 -832 -224" +"classname" "light" +} +{ +"mangle" "20 240 0" +"origin" "400 1048 240" +"classname" "info_intermission" +} +{ +"mangle" "20 145 0" +"origin" "-160 144 64" +"classname" "info_intermission" +} +{ +"mangle" "-20 45 0" +"origin" "-320 -824 -144" +"classname" "info_intermission" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*62" +} +{ +"classname" "item_artifact_super_damage" +"origin" "928 1768 -240" +"spawnflags" "1792" +} +{ +"classname" "light" +"origin" "8 1800 -120" +"light" "220" +} +{ +"classname" "weapon_lightning" +"origin" "1216 1784 -264" +"spawnflags" "1792" +} +{ +"classname" "item_cells" +"origin" "880 1648 -264" +"spawnflags" "1793" +} +{ +"spawnflags" "1793" +"origin" "880 1864 -264" +"classname" "item_cells" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*63" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*64" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*65" +} +{ +"classname" "info_player_coop" +"origin" "664 -1520 -312" +"angle" "180" +} +{ +"angle" "180" +"origin" "592 -1600 -312" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "680 -1712 -312" +"angle" "180" +} +{ +"classname" "air_bubbles" +"origin" "720 1384 -320" +} +{ +"classname" "light" +"origin" "680 1376 -312" +} +{ +"classname" "func_door_secret" +"angle" "180" +"spawnflags" "2" +"targetname" "t41" +"model" "*66" +"t_length" "65" // svdijk -- added to prevent z-fighting +} +{ +"classname" "trigger_multiple" +"target" "t41" +"model" "*67" +} +{ +"origin" "688 1176 -312" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"classname" "light" +"origin" "480 1408 -312" +} +{ +"origin" "448 1552 -312" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"classname" "light" +"origin" "840 1080 -312" +} +{ +"origin" "840 1080 -160" +"classname" "light" +"light" "200" +} +{ +"light" "200" +"classname" "light" +"origin" "840 1080 0" +} +{ +"classname" "light" +"origin" "840 1080 232" +"light" "200" +} +{ +"classname" "item_artifact_envirosuit" +"origin" "576 1440 -344" +} +{ +"classname" "item_artifact_invulnerability" +"origin" "544 1248 -344" +} +{ +"light" "200" +"origin" "840 936 232" +"classname" "light" +} +{ +"classname" "light" +"origin" "840 760 232" +"light" "150" +} +{ +"light" "120" +"origin" "808 600 232" +"classname" "light" +} +{ +"classname" "item_health" +"origin" "824 960 152" +} +{ +"origin" "848 880 152" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "808 768 152" +} +{ +"classname" "trigger_multiple" +"message" "Welcome to the Well of Wishes!" +"wait" "5" +"sounds" "1" +"model" "*68" +} +{ +"classname" "trigger_multiple" +"sounds" "1" +"wait" "3" +"message" "The Dopefish Lives!" +"model" "*69" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*70" +} +{ +"classname" "trigger_secret" +"model" "*71" +} +{ +"spawnflags" "1" +"origin" "-1312 1392 -80" +"classname" "item_spikes" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*72" +} +{ +"classname" "light" +"origin" "-544 600 -248" +"light" "200" +} +{ +"light" "200" +"classname" "light_torch_small_walltorch" +"origin" "194 -214 196" +"spawnflags" "2048" +} +{ +"light" "200" +"origin" "352 984 -328" +"classname" "light" +} +{ +"classname" "light" +"origin" "96 976 -328" +"light" "200" +} +{ +"classname" "light" +"origin" "640 776 -336" +"light" "200" +} +{ +"classname" "func_plat" +"spawnflags" "1" +"model" "*73" +} +{ +"classname" "monster_fish" +"origin" "656 352 -336" +"spawnflags" "256" +} +{ +"spawnflags" "256" +"origin" "432 424 -336" +"classname" "monster_fish" +} +{ +"classname" "monster_fish" +"origin" "296 968 -336" +"spawnflags" "256" +} +{ +"origin" "-48 800 -336" +"classname" "monster_fish" +} +{ +"classname" "monster_fish" +"origin" "-896 248 -312" +} +{ +"origin" "-744 328 -312" +"classname" "monster_fish" +} +{ +"classname" "path_corner" +"origin" "272 -1504 -264" +"targetname" "t42" +"target" "t43" +} +{ +"origin" "56 -1352 -264" +"classname" "path_corner" +"target" "t42" +"targetname" "t43" +} +{ +"classname" "item_health" +"origin" "312 -1336 -272" +} +{ +"origin" "544 -1488 -336" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "312 1464 -160" +} +{ +"origin" "56 1488 -192" +"classname" "item_health" +} +{ +"classname" "trigger_once" +"killtarget" "t44" +"spawnflags" "3072" +"model" "*74" +} +{ +"classname" "item_rockets" +"origin" "216 648 120" +"spawnflags" "1" +} +{ +"classname" "item_shells" +"origin" "136 648 120" +"spawnflags" "1" +} diff --git a/Misc/qs_pak/maps/e2m7.diff b/Misc/qs_pak/maps/e2m7.diff new file mode 100644 index 0000000..badaf2e --- /dev/null +++ b/Misc/qs_pak/maps/e2m7.diff @@ -0,0 +1,22 @@ +--- e2m7.ent ++++ e2m7@10a8.ent +@@ -1449,18 +1449,19 @@ + { + "targetname" "t119" + "classname" "func_door" + "wait" "-1" + "angle" "-1" + "speed" "30" + "message" "Go for a swim first..." + "sounds" "3" + "model" "*40" ++"origin" "-1 0 0" // svdijk -- added to prevent z-fighting + } + { + "classname" "func_button" + "angle" "0" + "wait" "-1" + "target" "t45" + "model" "*41" + } + { diff --git a/Misc/qs_pak/maps/e2m7@10a8.ent b/Misc/qs_pak/maps/e2m7@10a8.ent new file mode 100644 index 0000000..cd2c6fd --- /dev/null +++ b/Misc/qs_pak/maps/e2m7@10a8.ent @@ -0,0 +1,3633 @@ +{ +"wad" "gfx/tim.wad" +"classname" "worldspawn" +"sounds" "7" +"worldtype" "0" +"message" "the Underearth" +} +{ +"angle" "90" +"origin" "1136 -1100 -72" +"classname" "info_player_start" +} +{ +"origin" "1184 -776 -152" +"classname" "light" +"light" "150" +} +{ +"classname" "light" +"origin" "1704 -584 -184" +"light" "150" +} +{ +"classname" "light" +"origin" "1640 -688 -184" +"light" "150" +} +{ +"classname" "light" +"origin" "1696 -888 -192" +"light" "150" +} +{ +"classname" "light" +"origin" "1088 -960 -152" +"light" "150" +} +{ +"classname" "light" +"origin" "1248 -960 -152" +"light" "150" +} +{ +"classname" "light" +"origin" "1016 -768 -152" +"light" "100" +} +{ +"classname" "light" +"origin" "896 -920 -152" +"light" "150" +} +{ +"light" "100" +"origin" "1584 -208 -112" +"classname" "light" +} +{ +"light" "100" +"origin" "1776 -208 -112" +"classname" "light" +} +{ +"light" "150" +"origin" "1584 -88 -112" +"classname" "light" +} +{ +"origin" "1774 58 -76" +"classname" "light_torch_small_walltorch" +} +{ +"light" "100" +"origin" "1584 -488 -232" +"classname" "light" +} +{ +"light" "100" +"origin" "1768 -480 -232" +"classname" "light" +} +{ +"light" "150" +"origin" "1752 -112 -176" +"classname" "light" +} +{ +"light" "150" +"origin" "1592 -120 -176" +"classname" "light" +} +{ +"light" "150" +"origin" "1592 -248 -192" +"classname" "light" +} +{ +"light" "200" +"origin" "1768 -240 -192" +"classname" "light" +} +{ +"light" "150" +"origin" "1676 -220 -188" +"classname" "light" +} +{ +"light" "150" +"origin" "1672 -40 -136" +"classname" "light" +} +{ +"light" "150" +"origin" "1676 -376 -252" +"classname" "light" +} +{ +"light" "250" +"origin" "1112 952 -92" +"classname" "light" +} +{ +"light" "200" +"origin" "1280 928 -152" +"classname" "light" +} +{ +"classname" "light" +"origin" "704 952 -92" +"light" "250" +} +{ +"classname" "light" +"origin" "824 1112 -92" +"light" "200" +} +{ +"classname" "light" +"origin" "952 760 -92" +"light" "200" +} +{ +"classname" "light" +"origin" "824 760 -92" +"light" "200" +} +{ +"light" "250" +"origin" "952 1112 -92" +"classname" "light" +} +{ +"classname" "light" +"origin" "1128 -848 288" +"light" "500" +} +{ +"classname" "light" +"origin" "1144 -432 288" +} +{ +"classname" "light" +"origin" "864 -552 272" +"light" "200" +} +{ +"classname" "light" +"origin" "1392 -568 168" +"light" "200" +} +{ +"classname" "light" +"origin" "1416 -592 -24" +"light" "150" +} +{ +"classname" "light" +"origin" "888 -584 -24" +"light" "150" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1058 -466 -24" +"light" "225" +} +{ +"origin" "1214 -466 -24" +"classname" "light_torch_small_walltorch" +"light" "225" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1198 -66 40" +"light" "300" +} +{ +"classname" "light" +"origin" "1144 -204 172" +"light" "150" +} +{ +"classname" "light" +"origin" "1144 -292 -32" +"light" "150" +} +{ +"classname" "item_spikes" +"origin" "880 -592 -96" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "1068 -944 -96" +} +{ +"classname" "light" +"origin" "1128 -1084 96" +"light" "300" +} +{ +"classname" "light" +"origin" "888 -848 248" +"light" "150" +} +{ +"light" "150" +"origin" "1504 -896 248" +"classname" "light" +} +{ +"classname" "light" +"origin" "1304 -1048 24" +"light" "225" +} +{ +"classname" "light" +"origin" "1520 -872 -192" +"light" "150" +} +{ +"light" "100" +"origin" "1384 -776 -184" +"classname" "light" +} +{ +"classname" "light" +"origin" "1368 -912 -184" +"light" "100" +} +{ +"classname" "light" +"origin" "1584 496 148" +"light" "200" +} +{ +"light" "200" +"origin" "1488 496 148" +"classname" "light" +} +{ +"classname" "light" +"origin" "1688 164 148" +"light" "200" +} +{ +"light" "200" +"origin" "1352 496 148" +"classname" "light" +} +{ +"classname" "light" +"origin" "1608 496 -36" +"light" "200" +} +{ +"light" "200" +"origin" "1456 496 -36" +"classname" "light" +} +{ +"classname" "light" +"origin" "1692 600 -12" +"light" "200" +} +{ +"light" "200" +"origin" "1274 618 -64" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1274 378 -64" +"light" "200" +} +{ +"classname" "light" +"origin" "1256 496 -40" +"light" "200" +} +{ +"spawnflags" "2056" +"wait" "-1" +"classname" "func_door" +"angle" "270" +"model" "*1" +} +{ +"wait" "-1" +"spawnflags" "2056" +"sounds" "3" +"angle" "90" +"classname" "func_door" +"model" "*2" +} +{ +"light" "200" +"origin" "1968 1792 -257" +"classname" "light" +} +{ +"light" "250" +"origin" "1880 1792 -105" +"classname" "light" +} +{ +"classname" "light" +"origin" "1976 1944 56" +"light" "150" +} +{ +"light" "150" +"origin" "1992 1624 64" +"classname" "light" +} +{ +"classname" "light" +"origin" "1944 1736 64" +"light" "150" +} +{ +"light" "150" +"origin" "1944 1832 64" +"classname" "light" +} +{ +"classname" "light" +"origin" "1128 496 -24" +"light" "250" +} +{ +"light" "150" +"origin" "1120 632 -24" +"classname" "light" +} +{ +"light" "200" +"origin" "928 544 -24" +"classname" "light" +} +{ +"classname" "light" +"origin" "640 664 -8" +"light" "200" +} +{ +"light" "200" +"origin" "848 672 -8" +"classname" "light" +} +{ +"classname" "light" +"origin" "1024 544 -188" +"light" "175" +} +{ +"light" "250" +"origin" "64 192 136" +"classname" "light" +} +{ +"classname" "light" +"origin" "528 184 136" +"light" "250" +} +{ +"light" "200" +"origin" "72 408 8" +"classname" "light" +} +{ +"classname" "light" +"origin" "80 -48 8" +"light" "200" +} +{ +"light" "250" +"origin" "400 384 80" +"classname" "light" +} +{ +"classname" "light" +"origin" "392 -16 80" +"light" "250" +} +{ +"classname" "light" +"origin" "312 184 -80" +"light" "200" +} +{ +"light" "200" +"origin" "440 184 -80" +"classname" "light" +} +{ +"classname" "light" +"origin" "504 368 -120" +"light" "250" +} +{ +"light" "150" +"origin" "632 192 -120" +"classname" "light" +} +{ +"classname" "light" +"origin" "504 -16 -120" +"light" "250" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "774 446 -172" +"light" "250" +} +{ +"origin" "774 -70 -172" +"classname" "light_torch_small_walltorch" +"light" "250" +} +{ +"light" "250" +"origin" "896 -128 152" +"classname" "light" +} +{ +"classname" "light" +"origin" "896 184 152" +"light" "250" +} +{ +"light" "150" +"origin" "656 184 216" +"classname" "light" +} +{ +"classname" "light" +"origin" "304 368 -152" +"light" "200" +} +{ +"light" "200" +"origin" "0 480 -168" +"classname" "light" +} +{ +"classname" "light" +"origin" "96 376 -168" +"light" "200" +} +{ +"classname" "light" +"origin" "16 1480 -96" +"light" "200" +} +{ +"light" "200" +"origin" "1280 1824 -120" +"classname" "light" +} +{ +"light" "200" +"origin" "504 1816 -120" +"classname" "light" +} +{ +"classname" "light" +"origin" "712 1808 -120" +"light" "200" +} +{ +"light" "200" +"origin" "1064 1808 -120" +"classname" "light" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "858 1950 -172" +"light" "250" +} +{ +"origin" "658 1950 -172" +"classname" "light_torch_small_walltorch" +"light" "250" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "666 1682 -172" +"light" "250" +} +{ +"origin" "858 1682 -172" +"classname" "light_torch_small_walltorch" +"light" "250" +} +{ +"classname" "light" +"origin" "1248 1384 -32" +"light" "200" +} +{ +"classname" "light" +"origin" "1688 936 -136" +"light" "200" +} +{ +"light" "250" +"origin" "1856 1444 -52" +"classname" "light" +} +{ +"light" "150" +"origin" "1864 1316 -192" +"classname" "light" +} +{ +"classname" "light" +"origin" "1776 1212 -192" +"light" "150" +} +{ +"light" "150" +"origin" "1696 1076 -192" +"classname" "light" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1770 730 -64" +"light" "250" +} +{ +"classname" "light" +"origin" "1760 1720 -201" +"light" "250" +} +{ +"classname" "light" +"origin" "1632 1520 -201" +"light" "200" +} +{ +"classname" "light" +"origin" "1984 1504 -201" +"light" "200" +} +{ +"light" "250" +"classname" "light_torch_small_walltorch" +"origin" "1874 2104 -300" +} +{ +"light" "250" +"origin" "1712 2104 -300" +"classname" "light_torch_small_walltorch" +} +{ +"light" "200" +"classname" "light" +"origin" "1792 2048 48" +} +{ +"classname" "light_flame_large_yellow" +"origin" "1362 1778 0" +} +{ +"classname" "light" +"origin" "1408 1776 -124" +"light" "200" +} +{ +"classname" "light" +"origin" "1520 1880 -84" +"light" "175" +} +{ +"light" "175" +"origin" "1416 1512 -84" +"classname" "light" +} +{ +"classname" "light" +"origin" "1376 1568 -164" +"light" "150" +} +{ +"light" "150" +"origin" "1416 1888 -164" +"classname" "light" +} +{ +"classname" "light" +"origin" "1544 2072 -164" +"light" "150" +} +{ +"classname" "light" +"origin" "1552 1968 36" +"light" "250" +} +{ +"classname" "light" +"origin" "1416 1968 -16" +"light" "175" +} +{ +"light" "175" +"origin" "1416 2176 -16" +"classname" "light" +} +{ +"classname" "light" +"origin" "1240 2176 -16" +"light" "175" +} +{ +"light" "175" +"origin" "1240 2000 -16" +"classname" "light" +} +{ +"classname" "light" +"origin" "1264 1576 -72" +"light" "200" +} +{ +"light" "200" +"origin" "992 1480 -40" +"classname" "light" +} +{ +"light" "200" +"classname" "light_torch_small_walltorch" +"origin" "968 1632 -132" +} +{ +"light" "200" +"origin" "968 1328 -132" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1474 2234 -133" +"light" "200" +} +{ +"origin" "1182 2234 -133" +"classname" "light_torch_small_walltorch" +"light" "200" +} +{ +"classname" "light" +"origin" "1936 1480 48" +"light" "150" +} +{ +"light" "150" +"origin" "1544 1528 64" +"classname" "light" +} +{ +"classname" "light" +"origin" "1472 1488 24" +"light" "150" +} +{ +"classname" "light" +"origin" "1400 1664 96" +"light" "150" +} +{ +"light" "200" +"origin" "1792 2176 -221" +"classname" "light" +} +{ +"classname" "light" +"origin" "1880 2288 -221" +"light" "200" +} +{ +"light" "200" +"origin" "2048 2288 -221" +"classname" "light" +} +{ +"classname" "light" +"origin" "2128 2208 -221" +"light" "200" +} +{ +"light" "200" +"origin" "2160 1992 -205" +"classname" "light" +} +{ +"origin" "2288 1952 -29" +"classname" "light" +} +{ +"light" "250" +"origin" "2274 1738 -172" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "2274 1682 -172" +"light" "250" +} +{ +"light" "200" +"origin" "2376 2184 -152" +"classname" "light" +} +{ +"light" "200" +"origin" "2618 1658 -169" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "2618 1368 -169" +"light" "200" +} +{ +"light" "200" +"origin" "2176 1488 -169" +"classname" "light_torch_small_walltorch" +} +{ +"light" "200" +"origin" "2298 626 24" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1930 738 24" +"light" "200" +} +{ +"light" "200" +"origin" "2050 394 24" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "func_plat" +"model" "*3" +} +{ +"light" "150" +"origin" "2152 1784 -312" +"classname" "light" +} +{ +"classname" "light" +"origin" "2232 936 -4" +"light" "200" +} +{ +"classname" "light" +"origin" "2232 1040 48" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "2034 1034 -164" +"light" "200" +} +{ +"classname" "light" +"origin" "2304 1040 280" +"light" "200" +} +{ +"light" "200" +"origin" "2168 1040 280" +"classname" "light" +} +{ +"origin" "2130 2452 -112" +"classname" "light_flame_large_yellow" +"light" "250" +} +{ +"classname" "light_flame_large_yellow" +"origin" "1858 2452 -112" +"light" "250" +} +{ +"classname" "light" +"origin" "2132 2416 -188" +"light" "150" +} +{ +"light" "150" +"origin" "1860 2416 -188" +"classname" "light" +} +{ +"light" "200" +"origin" "2256 1968 -453" +"classname" "light" +} +{ +"classname" "light" +"origin" "2256 2184 -453" +"light" "200" +} +{ +"light" "200" +"origin" "2216 2384 -453" +"classname" "light" +} +{ +"classname" "light" +"origin" "1792 2400 -453" +"light" "200" +} +{ +"light" "175" +"origin" "1984 2400 -453" +"classname" "light" +} +{ +"light" "150" +"origin" "2168 1608 -9" +"classname" "light" +} +{ +"classname" "light" +"origin" "2368 1600 -9" +"light" "150" +} +{ +"light" "150" +"origin" "2240 1424 44" +"classname" "light" +} +{ +"classname" "light" +"origin" "2400 1424 44" +"light" "150" +} +{ +"light" "150" +"origin" "2560 1424 44" +"classname" "light" +} +{ +"classname" "light" +"origin" "2560 1560 44" +"light" "175" +} +{ +"light" "150" +"origin" "2232 1288 44" +"classname" "light" +} +{ +"light" "175" +"origin" "2384 1424 -160" +"classname" "light" +} +{ +"classname" "light" +"origin" "2232 1288 -160" +"light" "175" +} +{ +"light" "150" +"origin" "2164 932 -172" +"classname" "light" +} +{ +"classname" "light" +"origin" "2308 932 -172" +"light" "150" +} +{ +"light" "150" +"origin" "2232 776 24" +"classname" "light" +} +{ +"classname" "light" +"origin" "2192 664 24" +"light" "150" +} +{ +"light" "150" +"origin" "2016 696 24" +"classname" "light" +} +{ +"classname" "light" +"origin" "1912 496 24" +"light" "150" +} +{ +"light" "175" +"origin" "80 1616 -120" +"classname" "light" +} +{ +"classname" "light" +"origin" "72 1888 -120" +"light" "175" +} +{ +"light" "175" +"origin" "296 1616 -120" +"classname" "light" +} +{ +"light" "175" +"origin" "304 1888 -120" +"classname" "light" +} +{ +"targetname" "t20" +"angle" "90" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t21" +"angle" "120" +"classname" "trap_spikeshooter" +"origin" "192 1752 -208" +"spawnflags" "1" +} +{ +"targetname" "t22" +"angle" "150" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t19" +"angle" "60" +"classname" "trap_spikeshooter" +"origin" "192 1752 -208" +"spawnflags" "1" +} +{ +"targetname" "t18" +"angle" "30" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t17" +"angle" "0" +"classname" "trap_spikeshooter" +"origin" "192 1752 -208" +"spawnflags" "1" +} +{ +"targetname" "t24" +"angle" "210" +"classname" "trap_spikeshooter" +"origin" "192 1752 -208" +"spawnflags" "1" +} +{ +"targetname" "t23" +"angle" "180" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t25" +"angle" "240" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t26" +"angle" "270" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t27" +"angle" "300" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t28" +"angle" "330" +"spawnflags" "1" +"origin" "192 1752 -208" +"classname" "trap_spikeshooter" +} +{ +"delay" ".1" +"targetname" "t29" +"target" "t17" +"classname" "trigger_multiple" +"model" "*4" +} +{ +"delay" ".1" +"targetname" "t17" +"target" "t18" +"classname" "trigger_multiple" +"model" "*5" +} +{ +"delay" ".1" +"targetname" "t18" +"target" "t19" +"classname" "trigger_multiple" +"model" "*6" +} +{ +"delay" ".1" +"targetname" "t19" +"target" "t20" +"classname" "trigger_multiple" +"model" "*7" +} +{ +"delay" ".1" +"targetname" "t20" +"target" "t21" +"classname" "trigger_multiple" +"model" "*8" +} +{ +"delay" ".1" +"targetname" "t21" +"target" "t22" +"classname" "trigger_multiple" +"model" "*9" +} +{ +"delay" ".1" +"targetname" "t22" +"target" "t23" +"classname" "trigger_multiple" +"model" "*10" +} +{ +"delay" ".1" +"targetname" "t23" +"target" "t24" +"classname" "trigger_multiple" +"model" "*11" +} +{ +"delay" ".1" +"targetname" "t24" +"target" "t25" +"classname" "trigger_multiple" +"model" "*12" +} +{ +"delay" ".1" +"targetname" "t25" +"target" "t26" +"classname" "trigger_multiple" +"model" "*13" +} +{ +"delay" ".1" +"targetname" "t26" +"target" "t27" +"classname" "trigger_multiple" +"model" "*14" +} +{ +"delay" ".1" +"targetname" "t27" +"target" "t28" +"classname" "trigger_multiple" +"model" "*15" +} +{ +"target" "t29" +"wait" "1.3" +"classname" "trigger_multiple" +"model" "*16" +} +{ +"origin" "192 1750 -188" +"classname" "light_flame_large_yellow" +} +{ +"light" "125" +"origin" "214 1752 -166" +"classname" "light" +} +{ +"classname" "light" +"origin" "192 1774 -166" +"light" "125" +} +{ +"light" "125" +"origin" "170 1750 -166" +"classname" "light" +} +{ +"classname" "light" +"origin" "194 1726 -166" +"light" "125" +} +{ +"target" "t31" +"wait" "-1" +"angle" "0" +"classname" "func_button" +"model" "*17" +} +{ +"target" "t31" +"angle" "180" +"wait" "-1" +"classname" "func_button" +"model" "*18" +} +{ +"target" "t31" +"wait" "-1" +"angle" "180" +"classname" "func_button" +"model" "*19" +} +{ +"target" "t31" +"angle" "90" +"wait" "-1" +"classname" "func_button" +"model" "*20" +} +{ +"wait" "-1" +"targetname" "t30" +"sounds" "4" +"speed" "50" +"angle" "-1" +"classname" "func_door" +"model" "*21" +} +{ +"count" "4" +"targetname" "t31" +"target" "t30" +"classname" "trigger_counter" +"model" "*22" +} +{ +"light" "150" +"origin" "2424 1080 -176" +"classname" "light" +} +{ +"classname" "light" +"origin" "2424 992 -176" +"light" "150" +} +{ +"targetname" "t40" +"angle" "180" +"spawnflags" "1" +"origin" "2434 1036 -192" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t40" +"classname" "trap_spikeshooter" +"origin" "2434 1036 -192" +"spawnflags" "1" +"angle" "160" +} +{ +"targetname" "t40" +"angle" "140" +"spawnflags" "1" +"origin" "2434 1036 -192" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t40" +"classname" "trap_spikeshooter" +"origin" "2434 1036 -192" +"spawnflags" "1" +"angle" "120" +} +{ +"targetname" "t40" +"angle" "200" +"spawnflags" "1" +"origin" "2434 1036 -192" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t40" +"classname" "trap_spikeshooter" +"origin" "2434 1036 -192" +"spawnflags" "1" +"angle" "220" +} +{ +"targetname" "t40" +"angle" "240" +"spawnflags" "1" +"origin" "2434 1036 -192" +"classname" "trap_spikeshooter" +} +{ +"targetname" "t41" +"target" "t40" +"wait" ".5" +"classname" "trigger_multiple" +"model" "*23" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*24" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*25" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*26" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*27" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*28" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*29" +} +{ +"target" "t41" +"classname" "trigger_multiple" +"model" "*30" +} +{ +"light" "125" +"origin" "2196 1200 -204" +"classname" "light" +} +{ +"classname" "light" +"origin" "2284 1200 -204" +"light" "125" +} +{ +"light" "125" +"origin" "2284 1112 -204" +"classname" "light" +} +{ +"classname" "light" +"origin" "2388 1104 -204" +"light" "125" +} +{ +"light" "125" +"origin" "2284 1000 -204" +"classname" "light" +} +{ +"classname" "light" +"origin" "2140 1008 -204" +"light" "125" +} +{ +"light" "125" +"origin" "2132 1096 -204" +"classname" "light" +} +{ +"classname" "light" +"origin" "2132 1160 -204" +"light" "125" +} +{ +"light" "200" +"origin" "528 1816 -392" +"classname" "light" +} +{ +"classname" "light" +"origin" "736 1808 -392" +"light" "200" +} +{ +"light" "200" +"origin" "1040 1808 -392" +"classname" "light" +} +{ +"classname" "light" +"origin" "744 1424 -392" +"light" "200" +} +{ +"light" "200" +"origin" "752 1288 -416" +"classname" "light" +} +{ +"light" "200" +"origin" "760 1064 -376" +"classname" "light" +} +{ +"classname" "func_train" +"spawnflags" "33" +"targetname" "t42" +"dmg" "1000" +"sounds" "1" +"target" "t124" +"speed" "250" +"model" "*31" +} +{ +"classname" "func_door" +"angle" "-2" +"wait" "-1" +"targetname" "t43" +"speed" "50" +"sounds" "3" +"model" "*32" +} +{ +"classname" "func_button" +"angle" "-2" +"wait" "-1" +"target" "t42" +"sounds" "1" +"model" "*33" +} +{ +"classname" "trigger_once" +"target" "t43" +"targetname" "t42" +"delay" "2" +"model" "*34" +} +{ +"classname" "light" +"origin" "1936 1784 -288" +"light" "125" +} +{ +"classname" "monster_ogre" +"origin" "1976 1784 -328" +"angle" "180" +} +{ +"classname" "func_door" +"angle" "90" +"wait" "-1" +"sounds" "1" +"model" "*35" +} +{ +"classname" "func_door" +"angle" "270" +"targetname" "t44" +"wait" "-1" +"model" "*36" +} +{ +"classname" "light" +"origin" "64 264 8" +"light" "125" +} +{ +"light" "125" +"origin" "64 120 8" +"classname" "light" +} +{ +"classname" "light" +"origin" "64 192 8" +"light" "100" +} +{ +"classname" "trigger_once" +"target" "t44" +"model" "*37" +} +{ +"classname" "light" +"origin" "512 184 -8" +"light" "175" +} +{ +"targetname" "t119" +"classname" "func_door" +"angle" "90" +"wait" "-1" +"speed" "40" +"model" "*38" +} +{ +"targetname" "t119" +"classname" "func_door" +"wait" "-1" +"angle" "270" +"speed" "40" +"sounds" "4" +"model" "*39" +} +{ +"targetname" "t119" +"classname" "func_door" +"wait" "-1" +"angle" "-1" +"speed" "30" +"message" "Go for a swim first..." +"sounds" "3" +"model" "*40" +"origin" "-1 0 0" // svdijk -- added to prevent z-fighting +} +{ +"classname" "func_button" +"angle" "0" +"wait" "-1" +"target" "t45" +"model" "*41" +} +{ +"classname" "light" +"origin" "36 184 84" +"light" "75" +} +{ +"classname" "light" +"origin" "752 184 -192" +"light" "150" +} +{ +"classname" "light" +"origin" "544 536 -152" +"light" "125" +} +{ +"classname" "func_door" +"angle" "-2" +"sounds" "1" +"wait" "-1" +"targetname" "t45" +"model" "*42" +} +{ +"wait" "-1" +"sounds" "1" +"angle" "-2" +"classname" "func_door" +"targetname" "t45" +"model" "*43" +} +{ +"light" "125" +"origin" "544 -152 -152" +"classname" "light" +} +{ +"classname" "item_artifact_envirosuit" +"origin" "1024 492 -232" +} +{ +"classname" "item_armor1" +"origin" "2128 1752 -352" +} +{ +"classname" "light_flame_small_yellow" +"origin" "1024 368 -4" +"light" "250" +} +{ +"classname" "light" +"origin" "1024 400 -64" +"light" "150" +} +{ +"wait" "5" +"sounds" "1" +"classname" "func_door" +"angle" "-2" +"spawnflags" "1" +"targetname" "t51" +"model" "*44" +} +{ +"wait" "5" +"sounds" "1" +"classname" "func_door" +"angle" "-2" +"spawnflags" "1" +"targetname" "t51" +"model" "*45" +} +{ +"sounds" "3" +"classname" "func_button" +"angle" "270" +"wait" "3" +"target" "t51" +"model" "*46" +} +{ +"classname" "light" +"origin" "1224 712 -216" +"light" "100" +} +{ +"classname" "light" +"origin" "1232 768 -192" +"light" "100" +} +{ +"light" "75" +"origin" "1168 760 -232" +"classname" "light" +} +{ +"light" "100" +"origin" "1232 800 -208" +"classname" "light" +} +{ +"sounds" "1" +"origin" "1860 472 -8" +"classname" "item_key2" +"spawnflags" "2048" +} +{ +"classname" "light" +"origin" "1112 1568 -40" +"light" "200" +} +{ +"light" "150" +"origin" "-96 1440 -160" +"classname" "light" +} +{ +"classname" "light" +"origin" "384 1440 -160" +"light" "150" +} +{ +"light" "200" +"origin" "248 1408 -96" +"classname" "light" +} +{ +"light" "150" +"origin" "968 1480 -128" +"classname" "light" +} +{ +"light" "150" +"origin" "1008 -592 -32" +"classname" "light" +} +{ +"classname" "light" +"origin" "1264 -592 -32" +"light" "150" +} +{ +"light" "200" +"origin" "880 -840 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "1376 -856 -56" +"light" "200" +} +{ +"light" "175" +"origin" "264 184 -8" +"classname" "light" +} +{ +"light" "175" +"origin" "1464 -864 -208" +"classname" "light" +} +{ +"light" "175" +"origin" "1816 160 -56" +"classname" "light" +} +{ +"classname" "light" +"origin" "1560 160 -56" +"light" "175" +} +{ +"light" "175" +"origin" "1544 1632 104" +"classname" "light" +} +{ +"classname" "light" +"origin" "1792 1624 128" +"light" "175" +} +{ +"classname" "item_health" +"origin" "1068 -980 -104" +} +{ +"spawnflags" "1" +"classname" "item_shells" +"origin" "1344 -1160 -96" +} +{ +"classname" "monster_ogre" +"origin" "1008 -128 40" +"angle" "315" +"targetname" "t59" +"spawnflags" "1" +} +{ +"classname" "trigger_once" +"target" "t59" +"model" "*47" +} +{ +"classname" "monster_ogre" +"origin" "896 96 56" +"angle" "270" +"target" "t60" +"spawnflags" "1" +} +{ +"classname" "path_corner" +"origin" "896 208 40" +"target" "t60" +"targetname" "t61" +} +{ +"origin" "896 -16 40" +"classname" "path_corner" +"targetname" "t60" +"target" "t61" +} +{ +"classname" "path_corner" +"origin" "168 392 -56" +"target" "t62" +"targetname" "t63" +} +{ +"origin" "168 -16 -56" +"classname" "path_corner" +"targetname" "t62" +"target" "t63" +} +{ +"classname" "path_corner" +"origin" "88 344 -56" +"targetname" "t64" +"target" "t65" +"spawnflags" "256" +} +{ +"origin" "88 72 -56" +"classname" "path_corner" +"target" "t64" +"targetname" "t65" +"spawnflags" "256" +} +{ +"classname" "monster_hell_knight" +"origin" "88 224 -40" +"angle" "90" +"target" "t64" +"spawnflags" "257" +} +{ +"classname" "monster_hell_knight" +"origin" "168 232 -40" +"angle" "270" +"target" "t62" +"spawnflags" "1" +} +{ +"classname" "monster_zombie" +"origin" "544 -120 -208" +"angle" "90" +"targetname" "t45" +} +{ +"classname" "monster_zombie" +"origin" "544 496 -208" +"angle" "270" +"targetname" "t45" +} +{ +"classname" "monster_wizard" +"origin" "664 428 216" +"angle" "225" +"spawnflags" "1" +} +{ +"angle" "135" +"origin" "664 -56 216" +"classname" "monster_wizard" +"spawnflags" "257" +} +{ +"classname" "light" +"origin" "1736 88 -40" +"light" "100" +} +{ +"classname" "monster_ogre" +"origin" "904 -120 56" +"angle" "0" +"targetname" "t66" +"spawnflags" "1281" +} +{ +"classname" "trigger_once" +"target" "t66" +"spawnflags" "256" +"model" "*48" +} +{ +"classname" "item_health" +"origin" "1168 -408 -80" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "1168 -104 -16" +} +{ +"classname" "item_spikes" +"origin" "928 216 32" +} +{ +"classname" "item_rockets" +"origin" "632 -48 32" +} +{ +"classname" "item_health" +"origin" "32 392 -64" +} +{ +"light" "200" +"origin" "1688 288 148" +"classname" "light" +} +{ +"classname" "light" +"origin" "1688 464 148" +"light" "200" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "1814 622 -64" +"light" "250" +} +{ +"classname" "light" +"origin" "1768 344 -72" +"light" "200" +} +{ +"classname" "trigger_monsterjump" +"angle" "0" +"model" "*49" +} +{ +"targetname" "t100" +"classname" "monster_ogre" +"origin" "1504 272 24" +"angle" "0" +"spawnflags" "256" +} +{ +"targetname" "t68" +"target" "t67" +"origin" "1352 576 -120" +"classname" "path_corner" +"spawnflags" "256" +} +{ +"target" "t68" +"targetname" "t67" +"classname" "path_corner" +"origin" "1352 416 -120" +"spawnflags" "256" +} +{ +"target" "t68" +"angle" "90" +"origin" "1360 480 -104" +"classname" "monster_demon1" +"spawnflags" "257" +} +{ +"targetname" "t70" +"target" "t69" +"origin" "1472 496 -120" +"classname" "path_corner" +} +{ +"target" "t70" +"targetname" "t69" +"classname" "path_corner" +"origin" "1688 496 -120" +} +{ +"target" "t69" +"angle" "270" +"origin" "1648 544 -104" +"classname" "monster_ogre" +"spawnflags" "1" +} +{ +"target" "t71" +"targetname" "t72" +"origin" "1984 688 -24" +"classname" "path_corner" +"spawnflags" "256" +} +{ +"target" "t72" +"targetname" "t71" +"classname" "path_corner" +"origin" "1984 448 -24" +"spawnflags" "256" +} +{ +"target" "t71" +"angle" "270" +"origin" "1992 536 -8" +"classname" "monster_ogre" +"spawnflags" "257" +} +{ +"target" "t74" +"targetname" "t73" +"origin" "2120 680 -24" +"classname" "path_corner" +} +{ +"targetname" "t74" +"target" "t73" +"classname" "path_corner" +"origin" "2240 680 -24" +} +{ +"target" "t74" +"angle" "225" +"origin" "2256 736 -8" +"classname" "monster_ogre" +"spawnflags" "1" +} +{ +"targetname" "t78" +"angle" "90" +"origin" "2232 1312 -8" +"classname" "monster_ogre" +"spawnflags" "256" +} +{ +"target" "t76" +"angle" "90" +"origin" "2232 1008 -200" +"classname" "monster_hell_knight" +"spawnflags" "1" +} +{ +"targetname" "t76" +"target" "t75" +"origin" "2120 968 -216" +"classname" "path_corner" +} +{ +"target" "t76" +"targetname" "t75" +"classname" "path_corner" +"origin" "2336 968 -216" +} +{ +"classname" "light" +"origin" "2384 2400 -152" +"light" "200" +} +{ +"targetname" "t77" +"wait" "-1" +"sounds" "1" +"speed" "300" +"angle" "-2" +"classname" "func_door" +"model" "*50" +} +{ +"targetname" "t77" +"angle" "180" +"origin" "2512 2312 -200" +"classname" "monster_demon1" +"spawnflags" "256" +} +{ +"targetname" "t77" +"classname" "monster_demon1" +"origin" "2512 2160 -200" +"angle" "180" +} +{ +"classname" "item_health" +"origin" "2504 2200 -224" +} +{ +"light" "150" +"origin" "2536 2312 -128" +"classname" "light" +} +{ +"classname" "light" +"origin" "2536 2160 -128" +"light" "150" +} +{ +"target" "t77" +"classname" "trigger_once" +"model" "*51" +} +{ +"spawnflags" "2" +"origin" "2368 2336 -224" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "2504 2240 -224" +"classname" "item_spikes" +} +{ +"origin" "2408 2072 -224" +"classname" "item_shells" +} +{ +"target" "t78" +"classname" "trigger_once" +"model" "*52" +} +{ +"targetname" "t78" +"angle" "90" +"origin" "2240 1272 -200" +"classname" "monster_demon1" +} +{ +"light" "150" +"origin" "1120 1384 -40" +"classname" "light" +} +{ +"light" "200" +"origin" "1200 1248 -104" +"classname" "light" +} +{ +"light" "250" +"origin" "1288 1032 -4" +"classname" "light_flame_small_yellow" +} +{ +"light" "150" +"origin" "1256 1032 -64" +"classname" "light" +} +{ +"classname" "light_flame_small_yellow" +"origin" "568 1032 -4" +"light" "250" +} +{ +"classname" "light" +"origin" "600 1032 -64" +"light" "150" +} +{ +"light" "250" +"origin" "888 1272 -4" +"classname" "light_flame_small_yellow" +} +{ +"light" "150" +"origin" "888 1240 -64" +"classname" "light" +} +{ +"light" "200" +"origin" "608 1232 -108" +"classname" "light" +} +{ +"target" "t79" +"wait" "-1" +"angle" "180" +"classname" "func_button" +"model" "*53" +} +{ +"targetname" "t79" +"message" "This door is opened near by..." +"sounds" "3" +"speed" "35" +"angle" "-1" +"wait" "-1" +"classname" "func_door" +"model" "*54" +} +{ +"light" "100" +"origin" "580 1208 -148" +"classname" "light" +} +{ +"light" "200" +"origin" "1224 736 -8" +"classname" "light" +} +{ +"target" "t80" +"classname" "trigger_once" +"model" "*55" +} +{ +"targetname" "t80" +"angle" "315" +"origin" "640 1088 128" +"classname" "monster_wizard" +} +{ +"targetname" "t80" +"classname" "monster_wizard" +"origin" "616 720 128" +"angle" "0" +} +{ +"targetname" "t80" +"angle" "180" +"origin" "1280 680 128" +"classname" "monster_wizard" +"spawnflags" "256" +} +{ +"target" "t82" +"origin" "1248 1080 24" +"classname" "monster_wizard" +"spawnflags" "1" +} +{ +"targetname" "t82" +"target" "t81" +"origin" "1168 1064 8" +"classname" "path_corner" +} +{ +"target" "t82" +"targetname" "t81" +"classname" "path_corner" +"origin" "720 1064 8" +} +{ +"targetname" "t80" +"classname" "monster_wizard" +"origin" "584 1112 128" +"angle" "315" +"spawnflags" "256" +} +{ +"origin" "1256 944 -184" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "1116 584 -256" +"classname" "item_spikes" +} +{ +"targetname" "t79" +"angle" "315" +"origin" "1000 1464 -168" +"classname" "monster_ogre" +} +{ +"target" "t85" +"targetname" "t86" +"origin" "1456 1824 -192" +"classname" "path_corner" +"spawnflags" "256" +} +{ +"target" "t86" +"targetname" "t85" +"classname" "path_corner" +"origin" "1456 1560 -192" +"spawnflags" "256" +} +{ +"target" "t85" +"angle" "270" +"origin" "1456 1664 -176" +"classname" "monster_ogre" +"spawnflags" "256" +} +{ +"targetname" "t91" +"angle" "270" +"origin" "1560 1944 -320" +"classname" "monster_zombie" +"spawnflags" "256" +} +{ +"targetname" "t91" +"classname" "monster_zombie" +"origin" "1600 1912 -320" +"angle" "270" +} +{ +"angle" "225" +"origin" "1960 1984 -320" +"classname" "monster_zombie" +} +{ +"targetname" "t91" +"classname" "monster_zombie" +"origin" "1904 1928 -320" +"angle" "225" +} +{ +"targetname" "t91" +"angle" "270" +"origin" "1624 1768 -320" +"classname" "monster_zombie" +} +{ +"target" "t87" +"classname" "monster_zombie" +"origin" "1696 1912 -320" +"angle" "0" +} +{ +"target" "t89" +"angle" "270" +"origin" "1704 1800 -320" +"classname" "monster_zombie" +} +{ +"target" "t87" +"targetname" "t88" +"origin" "1648 1912 -336" +"classname" "path_corner" +} +{ +"target" "t88" +"targetname" "t87" +"classname" "path_corner" +"origin" "1848 1904 -336" +} +{ +"targetname" "t90" +"target" "t89" +"origin" "1648 1824 -336" +"classname" "path_corner" +} +{ +"target" "t90" +"targetname" "t89" +"classname" "path_corner" +"origin" "1768 1752 -336" +} +{ +"target" "t91" +"classname" "trigger_once" +"model" "*56" +} +{ +"spawnflags" "1" +"origin" "1496 1808 -200" +"classname" "item_health" +} +{ +"spawnflags" "1" +"origin" "1184 2176 -192" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "1184 2128 -192" +"spawnflags" "1" +} +{ +"spawnflags" "1" +"origin" "1264 1680 -192" +"classname" "item_health" +} +{ +"target" "t92" +"targetname" "t93" +"origin" "2376 2304 -216" +"classname" "path_corner" +} +{ +"target" "t93" +"targetname" "t92" +"classname" "path_corner" +"origin" "2376 1984 -216" +} +{ +"target" "t95" +"targetname" "t94" +"origin" "2144 1608 -216" +"classname" "path_corner" +} +{ +"target" "t94" +"targetname" "t95" +"classname" "path_corner" +"origin" "2376 1608 -216" +} +{ +"target" "t92" +"angle" "270" +"origin" "2376 2176 -200" +"classname" "monster_hell_knight" +"spawnflags" "1" +} +{ +"angle" "135" +"origin" "2416 1760 -200" +"classname" "monster_hell_knight" +"spawnflags" "257" +} +{ +"target" "t94" +"angle" "180" +"origin" "2320 1608 -200" +"classname" "monster_hell_knight" +"spawnflags" "1" +} +{ +"angle" "90" +"origin" "2152 1840 -200" +"classname" "monster_ogre" +"spawnflags" "257" +} +{ +"target" "t97" +"targetname" "t96" +"origin" "2096 2376 -216" +"classname" "path_corner" +"spawnflags" "256" +} +{ +"target" "t96" +"targetname" "t97" +"classname" "path_corner" +"origin" "2232 2208 -216" +"spawnflags" "256" +} +{ +"target" "t96" +"angle" "135" +"origin" "2200 2264 -200" +"classname" "monster_wizard" +"spawnflags" "257" +} +{ +"origin" "2104 1544 -224" +"classname" "item_shells" +} +{ +"origin" "2176 1240 -32" +"classname" "item_spikes" +} +{ +"classname" "item_spikes" +"origin" "2256 1240 -32" +} +{ +"origin" "1928 584 -32" +"classname" "item_shells" +} +{ +"spawnflags" "1" +"origin" "2040 1072 -32" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "2040 1024 -32" +"spawnflags" "1" +} +{ +"angle" "225" +"origin" "2352 1160 -200" +"classname" "monster_hell_knight" +"spawnflags" "256" +} +{ +"angle" "180" +"origin" "1864 1192 -216" +"classname" "monster_zombie" +} +{ +"classname" "monster_zombie" +"origin" "1776 1192 -216" +"angle" "180" +} +{ +"target" "t99" +"targetname" "t98" +"origin" "1688 1192 -232" +"classname" "path_corner" +} +{ +"target" "t98" +"targetname" "t99" +"classname" "path_corner" +"origin" "1688 1048 -232" +} +{ +"target" "t98" +"angle" "90" +"origin" "1688 1112 -216" +"classname" "monster_zombie" +} +{ +"origin" "536 416 32" +"classname" "item_shells" +} +{ +"target" "t100" +"classname" "trigger_once" +"spawnflags" "256" +"model" "*57" +} +{ +"origin" "1784 408 -128" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "1784 448 -128" +} +{ +"origin" "1736 728 -128" +"classname" "item_rockets" +} +{ +"target" "t101" +"sounds" "1" +"wait" "-1" +"angle" "270" +"classname" "func_button" +"model" "*58" +} +{ +"light" "250" +"origin" "1418 234 52" +"classname" "light_torch_small_walltorch" +} +{ +"targetname" "t101" +"classname" "trigger_secret" +"model" "*59" +} +{ +"targetname" "t102" +"angle" "180" +"classname" "trigger_monsterjump" +"model" "*60" +} +{ +"target" "t102" +"killtarget" "t102" +"classname" "trigger_once" +"model" "*61" +} +{ +"origin" "1568 1968 -352" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "1608 1968 -352" +} +{ +"origin" "2112 1776 -352" +"classname" "item_shells" +"spawnflags" "2048" +} +{ +"origin" "1920 2232 -352" +"classname" "item_spikes" +} +{ +"classname" "item_spikes" +"origin" "2008 2232 -352" +} +{ +"classname" "item_shells" +"origin" "2176 1440 -224" +} +{ +"target" "t66" +"classname" "trigger_once" +"model" "*62" +} +{ +"origin" "1744 0 -128" +"classname" "item_spikes" +"spawnflags" "1" +} +{ +"target" "t103" +"targetname" "t104" +"origin" "-56 1432 -224" +"classname" "path_corner" +"spawnflags" "256" +} +{ +"target" "t104" +"targetname" "t103" +"classname" "path_corner" +"origin" "312 1432 -224" +"spawnflags" "256" +} +{ +"target" "t103" +"origin" "40 1440 -208" +"classname" "monster_ogre" +"spawnflags" "257" +} +{ +"angle" "45" +"origin" "56 1616 -208" +"classname" "monster_hell_knight" +"spawnflags" "1" +} +{ +"target" "t105" +"targetname" "t106" +"origin" "520 1816 -232" +"classname" "path_corner" +} +{ +"target" "t106" +"targetname" "t105" +"classname" "path_corner" +"origin" "920 1816 -232" +} +{ +"target" "t105" +"origin" "624 1800 -216" +"classname" "monster_hell_knight" +"spawnflags" "1" +} +{ +"angle" "0" +"origin" "480 1824 64" +"classname" "monster_wizard" +"spawnflags" "257" +} +{ +"targetname" "t107" +"classname" "monster_wizard" +"origin" "656 1816 64" +"angle" "0" +"spawnflags" "1" +} +{ +"targetname" "t107" +"angle" "0" +"origin" "840 1816 64" +"classname" "monster_wizard" +"spawnflags" "257" +} +{ +"target" "t107" +"classname" "trigger_once" +"model" "*63" +} +{ +"origin" "1440 2200 -192" +"classname" "item_shells" +} +{ +"classname" "item_shells" +"origin" "1440 2160 -192" +} +{ +"spawnflags" "1" +"origin" "1184 1976 -192" +"classname" "item_spikes" +} +{ +"target" "t109" +"targetname" "t108" +"origin" "1240 2000 -184" +"classname" "path_corner" +} +{ +"target" "t108" +"targetname" "t109" +"classname" "path_corner" +"origin" "1240 1760 -184" +} +{ +"target" "t108" +"angle" "90" +"origin" "1240 1904 -168" +"classname" "monster_ogre" +"spawnflags" "1" +} +{ +"target" "t110" +"classname" "trigger_once" +"spawnflags" "256" +"model" "*64" +} +{ +"targetname" "t110" +"angle" "45" +"origin" "1216 2088 -168" +"classname" "monster_ogre" +"spawnflags" "1281" +} +{ +"classname" "item_health" +"origin" "1496 1768 -200" +} +{ +"target" "t111" +"classname" "trigger_once" +"model" "*65" +} +{ +"targetname" "t111" +"angle" "315" +"origin" "764 -60 -208" +"classname" "monster_zombie" +} +{ +"targetname" "t111" +"angle" "45" +"origin" "764 436 -208" +"classname" "monster_zombie" +} +{ +"targetname" "t111" +"angle" "225" +"origin" "284 -56 -208" +"classname" "monster_zombie" +"spawnflags" "256" +} +{ +"targetname" "t119" +"wait" "-1" +"speed" "40" +"angle" "90" +"classname" "func_door" +"model" "*66" +} +{ +"origin" "1280 592 -128" +"classname" "item_shells" +} +{ +"classname" "item_shells" +"origin" "576 416 32" +} +{ +"classname" "item_spikes" +"origin" "904 504 -128" +"spawnflags" "1" +} +{ +"origin" "904 464 -128" +"classname" "item_spikes" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "1024 912 -152" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "720 848 -184" +"spawnflags" "1" +} +{ +"classname" "path_corner" +"origin" "1224 1192 -176" +"targetname" "t112" +"target" "t113" +} +{ +"origin" "1216 896 -176" +"classname" "path_corner" +"targetname" "t113" +"target" "t112" +} +{ +"classname" "monster_ogre" +"origin" "1224 992 -160" +"angle" "90" +"target" "t112" +} +{ +"classname" "item_shells" +"origin" "968 1600 -184" +} +{ +"classname" "item_artifact_super_damage" +"origin" "1444 308 -104" +} +{ +"classname" "light" +"origin" "992 -128 72" +"light" "100" +} +{ +"classname" "item_shells" +"origin" "1672 1008 -240" +} +{ +"classname" "item_health" +"origin" "2264 1320 -224" +} +{ +"classname" "monster_wizard" +"origin" "2120 1664 -72" +"angle" "315" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "2016 392 -32" +} +{ +"classname" "monster_hell_knight" +"origin" "360 1744 -208" +"angle" "90" +"targetname" "t114" +"spawnflags" "257" +} +{ +"classname" "trigger_once" +"target" "t114" +"spawnflags" "256" +"model" "*67" +} +{ +"classname" "item_health" +"origin" "352 1464 -232" +} +{ +"spawnflags" "1" +"origin" "352 1392 -232" +"classname" "item_health" +} +{ +"classname" "item_armorInv" +"origin" "744 1424 -448" +} +{ +"classname" "item_health" +"origin" "-56 320 -232" +} +{ +"origin" "-16 320 -232" +"classname" "item_health" +} +{ +"targetname" "t117" +"classname" "trigger_teleport" +"target" "t115" +"spawnflags" "2" +"model" "*68" +} +{ +"delay" ".5" +"targetname" "t117" +"classname" "trigger_teleport" +"target" "t116" +"spawnflags" "2" +"model" "*69" +} +{ +"classname" "monster_wizard" +"origin" "2928 1816 -152" +"angle" "180" +"targetname" "t117" +} +{ +"angle" "180" +"origin" "2928 1768 -152" +"classname" "monster_wizard" +"targetname" "t117" +} +{ +"classname" "info_teleport_destination" +"origin" "1824 1920 -184" +"angle" "225" +"targetname" "t115" +} +{ +"classname" "info_teleport_destination" +"origin" "1880 1544 -184" +"angle" "180" +"targetname" "t116" +} +{ +"classname" "trigger_once" +"target" "t117" +"model" "*70" +} +{ +"classname" "monster_zombie" +"origin" "764 388 -208" +"angle" "0" +"targetname" "t111" +} +{ +"angle" "0" +"origin" "764 -12 -208" +"classname" "monster_zombie" +"targetname" "t111" +} +{ +"classname" "monster_zombie" +"origin" "408 -56 -208" +"angle" "270" +"targetname" "t111" +} +{ +"classname" "light" +"origin" "1200 672 -240" +"light" "125" +} +{ +"classname" "item_spikes" +"origin" "72 392 -64" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "2368 920 -224" +} +{ +"origin" "2032 976 -224" +"classname" "item_spikes" +} +{ +"classname" "light" +"origin" "1416 2096 -112" +"light" "150" +} +{ +"light" "150" +"origin" "1240 2096 -112" +"classname" "light" +} +{ +"classname" "item_spikes" +"origin" "464 1824 -240" +} +{ +"origin" "504 1824 -240" +"classname" "item_spikes" +} +{ +"classname" "item_shells" +"origin" "-96 1472 -232" +"spawnflags" "1" +} +{ +"classname" "item_health" +"origin" "528 -172 -232" +} +{ +"classname" "item_health" +"origin" "40 -64 -64" +} +{ +"light" "225" +"classname" "light_torch_small_walltorch" +"origin" "122 -86 -8" +} +{ +"origin" "134 462 -8" +"classname" "light_torch_small_walltorch" +"light" "225" +} +{ +"light" "250" +"origin" "678 446 92" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light_torch_small_walltorch" +"origin" "678 -70 92" +"light" "250" +} +{ +"light" "150" +"origin" "120 -56 -16" +"classname" "light" +} +{ +"classname" "light" +"origin" "136 424 -16" +"light" "150" +} +{ +"light" "150" +"origin" "600 184 296" +"classname" "light" +} +{ +"classname" "light" +"origin" "152 184 296" +"light" "150" +} +{ +"light" "150" +"origin" "368 376 296" +"classname" "light" +} +{ +"classname" "light" +"origin" "368 0 296" +"light" "150" +} +{ +"light" "150" +"origin" "352 192 232" +"classname" "light" +} +{ +"light" "200" +"origin" "64 408 168" +"classname" "light" +} +{ +"classname" "light" +"origin" "56 -48 168" +"light" "200" +} +{ +"light" "125" +"origin" "1520 1880 24" +"classname" "light" +} +{ +"origin" "272 272 -232" +"classname" "item_rockets" +} +{ +"targetname" "t45" +"classname" "func_door" +"angle" "-2" +"sounds" "1" +"wait" "-1" +"model" "*71" +} +{ +"classname" "light" +"origin" "416 -152 -152" +"light" "125" +} +{ +"targetname" "t45" +"angle" "90" +"origin" "416 -120 -208" +"classname" "monster_zombie" +"spawnflags" "256" +} +{ +"origin" "400 524 -232" +"classname" "item_health" +} +{ +"light" "125" +"origin" "416 536 -152" +"classname" "light" +} +{ +"targetname" "t45" +"wait" "-1" +"sounds" "1" +"angle" "-2" +"classname" "func_door" +"model" "*72" +} +{ +"targetname" "t45" +"angle" "270" +"origin" "416 496 -208" +"classname" "monster_zombie" +"spawnflags" "256" +} +{ +"target" "t120" +"wait" "-1" +"angle" "270" +"classname" "func_button" +"model" "*73" +} +{ +"target" "t120" +"wait" "-1" +"angle" "90" +"classname" "func_button" +"model" "*74" +} +{ +"targetname" "t120" +"target" "t119" +"classname" "trigger_counter" +"model" "*75" +} +{ +"light" "125" +"origin" "1792 840 -112" +"classname" "light" +} +{ +"classname" "light" +"origin" "1584 840 -112" +"light" "125" +} +{ +"light" "125" +"origin" "1792 2280 -480" +"classname" "light" +} +{ +"classname" "light" +"origin" "1792 2144 -480" +"light" "125" +} +{ +"origin" "1776 2232 -508" +"classname" "item_health" +} +{ +"classname" "item_health" +"origin" "1776 2192 -508" +} +{ +"light" "150" +"origin" "1016 1696 -336" +"classname" "light" +} +{ +"light" "125" +"origin" "1080 1696 -280" +"classname" "light" +} +{ +"light" "125" +"origin" "1152 1696 -200" +"classname" "light" +} +{ +"light" "150" +"origin" "1096 1696 -352" +"classname" "light" +} +{ +"classname" "trigger_secret" +"model" "*76" +} +{ +"target" "t116" +"classname" "trigger_teleport" +"model" "*77" +} +{ +"spawnflags" "768" +"angle" "315" +"origin" "1400 1856 -176" +"classname" "monster_ogre" +} +{ +"target" "t121" +"targetname" "t122" +"spawnflags" "768" +"origin" "1824 2280 -344" +"classname" "path_corner" +} +{ +"target" "t122" +"targetname" "t121" +"spawnflags" "768" +"classname" "path_corner" +"origin" "2080 2280 -344" +} +{ +"target" "t121" +"spawnflags" "769" +"angle" "180" +"origin" "2128 2272 -328" +"classname" "monster_demon1" +} +{ +"spawnflags" "2816" +"origin" "1824 2072 -352" +"classname" "item_shells" +} +{ +"spawnflags" "769" +"angle" "90" +"origin" "1792 2176 -144" +"classname" "monster_wizard" +} +{ +"spawnflags" "769" +"angle" "180" +"origin" "2600 1640 -200" +"classname" "monster_hell_knight" +} +{ +"spawnflags" "2816" +"origin" "2096 1136 -32" +"classname" "item_shells" +} +{ +"classname" "monster_wizard" +"origin" "1872 1432 -72" +"angle" "90" +"spawnflags" "769" +} +{ +"classname" "monster_hell_knight" +"origin" "2400 1032 -8" +"angle" "180" +"spawnflags" "769" +} +{ +"classname" "monster_demon1" +"origin" "1000 496 -104" +"angle" "0" +"spawnflags" "768" +} +{ +"classname" "monster_demon1" +"origin" "1240 2088 -168" +"angle" "45" +"spawnflags" "769" +"targetname" "t110" +} +{ +"classname" "monster_hell_knight" +"origin" "360 1936 -208" +"angle" "270" +"spawnflags" "769" +"targetname" "t114" +} +{ +"classname" "item_shells" +"origin" "16 1768 -232" +"spawnflags" "2560" +} +{ +"spawnflags" "2560" +"origin" "344 1576 -232" +"classname" "item_shells" +} +{ +"classname" "trigger_changelevel" +"map" "e2m4" +"model" "*78" +} +{ +"classname" "light" +"origin" "1136 -1144 264" +"light" "250" +} +{ +"light" "250" +"origin" "1264 -552 44" +"classname" "light_flame_small_yellow" +} +{ +"classname" "light_flame_small_yellow" +"origin" "1008 -552 44" +"light" "250" +} +{ +"classname" "monster_demon1" +"origin" "888 -120 56" +"angle" "0" +"spawnflags" "769" +"targetname" "t66" +} +{ +"classname" "monster_hell_knight" +"origin" "616 184 56" +"angle" "0" +"spawnflags" "768" +} +{ +"classname" "item_spikes" +"origin" "880 216 32" +"spawnflags" "2816" +} +{ +"classname" "monster_hell_knight" +"origin" "1944 728 -8" +"angle" "0" +"spawnflags" "769" +} +{ +"classname" "light" +"origin" "-48 1184 -156" +"light" "200" +"style" "10" +} +{ +"style" "10" +"light" "200" +"origin" "0 1024 -120" +"classname" "light" +} +{ +"classname" "light" +"origin" "48 800 -120" +"light" "200" +"style" "10" +} +{ +"style" "10" +"light" "200" +"origin" "0 600 -120" +"classname" "light" +} +{ +"classname" "monster_hell_knight" +"origin" "128 1088 -208" +"angle" "225" +"spawnflags" "1" +} +{ +"angle" "315" +"origin" "-104 760 -208" +"classname" "monster_hell_knight" +"spawnflags" "1" +} +{ +"classname" "item_spikes" +"origin" "-56 592 -232" +} +{ +"classname" "item_health" +"origin" "-56 920 -232" +} +{ +"classname" "monster_demon1" +"origin" "0 544 -208" +"angle" "90" +"target" "t123" +"spawnflags" "769" +} +{ +"mangle" "20 315 0" +"origin" "1568 2040 -88" +"classname" "info_intermission" +} +{ +"classname" "item_shells" +"origin" "1528 1968 -352" +"spawnflags" "3584" +} +{ +"origin" "-88 1376 -232" +"classname" "item_health" +"spawnflags" "3585" +} +{ +"classname" "item_artifact_envirosuit" +"origin" "1216 1696 -168" +"spawnflags" "3584" +} +{ +"classname" "monster_demon1" +"origin" "144 372 -208" +"angle" "180" +"spawnflags" "768" +"targetname" "t123" +} +{ +"classname" "monster_demon1" +"origin" "0 528 -208" +"angle" "90" +"spawnflags" "1025" +} +{ +"classname" "info_player_deathmatch" +"origin" "1128 -840 -80" +"angle" "90" +} +{ +"classname" "info_player_deathmatch" +"origin" "656 184 -208" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "-24 1440 -208" +"angle" "0" +} +{ +"classname" "info_player_deathmatch" +"origin" "1240 1816 -168" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "1032 1568 -168" +"angle" "0" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*79" +} +{ +"classname" "weapon_rocketlauncher" +"origin" "184 1440 -232" +"spawnflags" "1792" +} +{ +"classname" "info_player_deathmatch" +"origin" "1424 608 -104" +"angle" "270" +} +{ +"classname" "info_player_deathmatch" +"origin" "2272 680 -8" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "2240 1472 -200" +"angle" "270" +} +{ +"classname" "weapon_supernailgun" +"origin" "2236 1184 -32" +"spawnflags" "1792" +} +{ +"classname" "info_player_deathmatch" +"origin" "2424 1824 -200" +"angle" "180" +} +{ +"classname" "info_player_deathmatch" +"origin" "1792 2072 -328" +"angle" "270" +} +{ +"classname" "weapon_supershotgun" +"origin" "1488 1584 -200" +"spawnflags" "1792" +} +{ +"classname" "weapon_grenadelauncher" +"origin" "88 184 -64" +"spawnflags" "1792" +} +{ +"classname" "info_player_deathmatch" +"origin" "1224 840 -168" +"angle" "90" +} +{ +"classname" "weapon_nailgun" +"origin" "1024 872 -152" +"spawnflags" "1792" +} +{ +"classname" "weapon_supershotgun" +"origin" "136 1760 -232" +"spawnflags" "1792" +} +{ +"classname" "weapon_nailgun" +"origin" "1976 2288 -352" +"spawnflags" "1792" +} +{ +"classname" "weapon_lightning" +"origin" "2128 1792 -352" +"spawnflags" "1792" +} +{ +"classname" "item_cells" +"origin" "1608 728 -128" +"spawnflags" "1792" +} +{ +"spawnflags" "1792" +"classname" "item_cells" +"origin" "72 1032 -232" +} +{ +"classname" "item_cells" +"origin" "1008 1328 -192" +"spawnflags" "1792" +} +{ +"classname" "item_cells" +"origin" "1304 -1160 -96" +"spawnflags" "1792" +} +{ +"origin" "2272 1440 -224" +"classname" "item_shells" +"spawnflags" "2816" +} +{ +"origin" "224 1736 -232" +"classname" "item_health" +} +{ +"classname" "info_intermission" +"origin" "1328 -1168 192" +"mangle" "20 120 0" +} +{ +"classname" "info_intermission" +"origin" "1248 680 8" +"mangle" "20 130 0" +} +{ +"classname" "info_intermission" +"origin" "1280 1824 -104" +"mangle" "10 180 0" +} +{ +"classname" "light" +"origin" "-304 888 -80" +"light" "150" +} +{ +"light" "150" +"origin" "-304 712 -80" +"classname" "light" +} +{ +"classname" "light" +"origin" "-224 872 -8" +"light" "125" +} +{ +"classname" "light" +"origin" "-224 728 -8" +"light" "125" +} +{ +"light" "150" +"origin" "-178 706 -156" +"classname" "light_torch_small_walltorch" +} +{ +"classname" "light" +"origin" "-320 838 -138" +"light" "100" +} +{ +"light" "100" +"origin" "-320 774 -138" +"classname" "light" +} +{ +"classname" "info_player_coop" +"origin" "1192 -1088 -72" +"angle" "90" +} +{ +"angle" "90" +"origin" "1080 -1088 -72" +"classname" "info_player_coop" +} +{ +"classname" "info_player_coop" +"origin" "1008 -1112 -72" +"angle" "90" +} +{ +"angle" "90" +"origin" "1264 -1112 -72" +"classname" "info_player_coop" +} +{ +"classname" "item_armor1" +"origin" "784 1816 -232" +} +{ +"classname" "item_spikes" +"origin" "-56 1472 -232" +"spawnflags" "1" +} +{ +"classname" "item_rockets" +"origin" "400 -172 -232" +} +{ +"classname" "func_wall" +"spawnflags" "1792" +"model" "*80" +} +{ +"spawnflags" "1792" +"classname" "func_wall" +"model" "*81" +} +{ +"classname" "path_corner" +"origin" "1954 1770 -96" +"targetname" "t124" +"target" "t125" +} +{ +"classname" "path_corner" +"origin" "1954 1770 -320" +"targetname" "t125" +"target" "t124" +} +{ +"classname" "weapon_grenadelauncher" +"origin" "1688 720 -128" +"spawnflags" "2048" +} +{ +"wait" "-1" +"angle" "-2" +"classname" "func_door" +"targetname" "t126" +"lip" "-8" +"model" "*82" +} +{ +"classname" "func_door_secret" +"angle" "90" +"spawnflags" "2" +"model" "*83" +} +{ +"classname" "light" +"origin" "-224 832 -152" +"light" "125" +} +{ +"classname" "trigger_counter" +"target" "t126" +"targetname" "t127" +"spawnflags" "1" +"count" "7" +"model" "*84" +} +{ +"classname" "trigger_once" +"health" "1" +"target" "t127" +"model" "*85" +} +{ +"health" "1" +"classname" "trigger_once" +"target" "t127" +"model" "*86" +} +{ +"classname" "trigger_once" +"health" "1" +"target" "t127" +"model" "*87" +} +{ +"health" "1" +"classname" "trigger_once" +"target" "t127" +"model" "*88" +} +{ +"classname" "trigger_once" +"health" "1" +"target" "t127" +"model" "*89" +} +{ +"health" "1" +"classname" "trigger_once" +"target" "t127" +"model" "*90" +} +{ +"classname" "trigger_once" +"health" "1" +"target" "t127" +"model" "*91" +} +{ +"classname" "ambient_swamp1" +"origin" "1338 -854 -104" +} +{ +"classname" "ambient_swamp2" +"origin" "938 -854 -104" +} +{ +"classname" "ambient_drip" +"origin" "1138 -854 -176" +} +{ +"classname" "ambient_drip" +"origin" "1650 -862 -192" +} +{ +"classname" "ambient_drip" +"origin" "1674 -438 -192" +} +{ +"classname" "ambient_drip" +"origin" "1682 2 -48" +} +{ +"classname" "ambient_swamp1" +"origin" "1674 1986 -280" +} +{ +"classname" "ambient_swamp2" +"origin" "1826 2378 -280" +} +{ +"classname" "ambient_swamp1" +"origin" "2258 2058 -280" +} +{ +"classname" "ambient_drip" +"origin" "746 1370 -376" +} +{ +"classname" "ambient_drip" +"origin" "762 906 -240" +} +{ +"origin" "1034 722 -240" +"classname" "ambient_drip" +} +{ +"classname" "ambient_drip" +"origin" "554 1818 -352" +} +{ +"origin" "1002 1810 -352" +"classname" "ambient_drip" +} +{ +"speed" "35" +"classname" "func_door" +"wait" "-1" +"angle" "-2" +"sounds" "1" +"targetname" "t101" +"model" "*92" +} +{ +"classname" "light" +"origin" "1440 296 -80" +"light" "125" +} +{ +"origin" "1792 792 -240" +"classname" "item_spikes" +} diff --git a/Misc/qs_pak/mkpak.sh b/Misc/qs_pak/mkpak.sh new file mode 100755 index 0000000..e6562bf --- /dev/null +++ b/Misc/qs_pak/mkpak.sh @@ -0,0 +1,135 @@ +#!/bin/sh +# +# Copyright (c) 2014, Sander van Dijk +# +# Permission to use, copy, modify, and/or 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. + +error() { + echo "$(basename "$0"): $*" >&2 + exit 1 +} + +assert_valid_stdout() { + if test -t 1 + then + error "Usage: $(basename "$0") [file ...] > output.pak" + fi +} + +assert_valid_file() { + if test ! -e "$1" + then + error "$1: No such file" + fi + if test ! -f "$1" + then + error "$f: Not a regular file" + fi + if test ! -r "$1" + then + error "$1: Permission denied" + fi + if test $(echo -n "$1" | wc -c) -gt 55 + then + error "$1: Name too long" + fi +} + +assert_valid_int32() { + if test $1 -lt -2147483648 -o $1 -gt 2147483647 + then + error "Too much data" + fi +} + +octal() { + if test $1 -gt 7 + then + octal $(expr $1 / 8) + fi + echo -n $(expr $1 % 8) +} + +byte() { + echo -en \\0$(octal $1) +} + +little_endian_uint32() { + byte $(expr $1 % 256) + byte $(expr $1 / 256 % 256) + byte $(expr $1 / 65536 % 256) + byte $(expr $1 / 16777216 % 256) +} + +little_endian_int32() { + if test $1 -lt 0 + then + little_endian_uint32 $(expr $1 + 4294967296) + else + little_endian_uint32 $1 + fi +} + +zero_padding() { + if test $1 -lt 1 + then + return + fi + byte 0 + zero_padding $(expr $1 - 1) +} + +header() { + echo -n PACK + little_endian_int32 $1 + little_endian_int32 $2 +} + +directory_entry() { + echo -n "$1" + zero_padding $(expr 56 - $(echo -n "$1" | wc -c)) + little_endian_int32 $2 + little_endian_int32 $3 +} + +assert_valid_stdout + +directory_offset=12 +directory_size=0 +for file in "$@" +do + assert_valid_file "$file" + file_offset=$directory_offset + assert_valid_int32 $file_offset + file_size=$(wc -c < "$file") + assert_valid_int32 $file_size + directory_offset=$(expr $directory_offset + $file_size) + assert_valid_int32 $directory_offset + directory_size=$(expr $directory_size + 64) + assert_valid_int32 $directory_size +done + +header $directory_offset $directory_size + +for file in "$@" +do + cat "$file" +done + +file_offset=12 +for file in "$@" +do + file_size=$(wc -c < "$file") + directory_entry "$file" $file_offset $file_size + file_offset=$(expr $file_offset + $file_size) +done diff --git a/Misc/systest.c b/Misc/systest.c new file mode 100644 index 0000000..2b512cb --- /dev/null +++ b/Misc/systest.c @@ -0,0 +1,256 @@ +/* + * stupid test tool that reports the type sizes and + * their alignment offsets in structures, and the byte + * order as detected at runtime and compile time. + */ + +/* + * endianness stuff: is supposed + * to succeed in locating the correct endian.h + * this BSD style may not work everywhere. + */ + +#undef ENDIAN_GUESSED_SAFE +#undef ENDIAN_ASSUMED_UNSAFE + +#include + +#include +#include +#include + +/* include more if it didn't work: */ +#if !defined(BYTE_ORDER) + +# if defined(__linux__) || defined(__linux) +# include +# elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +# include +# elif defined(__sun) || defined(__svr4__) +# include +# elif defined(_AIX) +# include +# elif defined(sgi) +# include +# elif defined(__DJGPP__) +# include +# endif + +#endif /* endian includes */ + + +#if defined(__BYTE_ORDER) && !defined(BYTE_ORDER) +#define BYTE_ORDER __BYTE_ORDER +#endif /* __BYTE_ORDER */ + +#if !defined(PDP_ENDIAN) +#if defined(__PDP_ENDIAN) +#define PDP_ENDIAN __PDP_ENDIAN +#else +#define PDP_ENDIAN 3412 +#endif +#endif /* NUXI endian (not supported) */ + +#if defined(__LITTLE_ENDIAN) && !defined(LITTLE_ENDIAN) +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif /* __LITTLE_ENDIAN */ + +#if defined(__BIG_ENDIAN) && !defined(BIG_ENDIAN) +#define BIG_ENDIAN __BIG_ENDIAN +#endif /* __LITTLE_ENDIAN */ + + +#if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN) + +# if (BYTE_ORDER != LITTLE_ENDIAN) && (BYTE_ORDER != BIG_ENDIAN) +# error "Unsupported endianness." +# endif + +#else /* one of the definitions is mising. */ + +# undef BYTE_ORDER +# undef LITTLE_ENDIAN +# undef BIG_ENDIAN +# undef PDP_ENDIAN +# define LITTLE_ENDIAN 1234 +# define BIG_ENDIAN 4321 +# define PDP_ENDIAN 3412 + +#endif /* byte order defs */ + + +#if !defined(BYTE_ORDER) +/* supposedly safe assumptions: these may actually + * be OS dependant and listing all possible compiler + * macros here is impossible (the ones here are gcc + * flags, mostly.) so, proceed carefully.. + */ + +# if defined(__DJGPP__) || defined(MSDOS) || defined(__MSDOS__) +# define BYTE_ORDER LITTLE_ENDIAN /* DOS */ + +# elif defined(__sun) || defined(__svr4__) /* solaris */ +# if defined(_LITTLE_ENDIAN) /* x86 */ +# define BYTE_ORDER LITTLE_ENDIAN +# elif defined(_BIG_ENDIAN) /* sparc */ +# define BYTE_ORDER BIG_ENDIAN +# endif + +# elif defined(__i386) || defined(__i386__) || defined(__386__) || defined(_M_IX86) +# define BYTE_ORDER LITTLE_ENDIAN /* any x86 */ + +# elif defined(__amd64) || defined(__x86_64__) || defined(_M_X64) +# define BYTE_ORDER LITTLE_ENDIAN /* any x64 */ + +# elif defined(_M_IA64) +# define BYTE_ORDER LITTLE_ENDIAN /* ia64 / Visual C */ + +# elif defined (__ppc__) || defined(__POWERPC__) || defined(_M_PPC) +# define BYTE_ORDER BIG_ENDIAN /* PPC: big endian */ + +# elif (defined(__alpha__) || defined(__alpha)) || defined(_M_ALPHA) +# define BYTE_ORDER LITTLE_ENDIAN /* should be safe */ + +# elif defined(_WIN32) || defined(_WIN64) /* windows : */ +# define BYTE_ORDER LITTLE_ENDIAN /* should be safe */ + +# elif defined(__hppa) || defined(__hppa__) || defined(__sparc) || defined(__sparc__) /* others: check! */ +# define BYTE_ORDER BIG_ENDIAN + +# endif + +# if defined(BYTE_ORDER) + /* raise a flag, just in case: */ +# define ENDIAN_GUESSED_SAFE BYTE_ORDER +# endif + +#endif /* supposedly safe assumptions */ + + +#if !defined(BYTE_ORDER) + +/* brain-dead fallback: default to little endian. + * change if necessary!!!! + */ +# define BYTE_ORDER LITTLE_ENDIAN +# define ENDIAN_ASSUMED_UNSAFE BYTE_ORDER + +#endif /* fallback. */ + +#if defined(ENDIAN_ASSUMED_UNSAFE) +/* +# if (ENDIAN_ASSUMED_UNSAFE == LITTLE_ENDIAN) +# warning "Cannot determine endianess. Using LIL endian as an UNSAFE default" +# elif (ENDIAN_ASSUMED_UNSAFE == PDP_ENDIAN) +# warning "Cannot determine endianess. Using PDP (NUXI) as an UNSAFE default." +# elif (ENDIAN_ASSUMED_UNSAFE == BIG_ENDIAN) +# warning "Cannot determine endianess. Using BIG endian as an UNSAFE default." +# endif +*/ +#endif /* ENDIAN_ASSUMED_UNSAFE */ + +#define COMPILED_BYTEORDER BYTE_ORDER + +#include +#include + +int DetectByteorder (void) +{ + int i = 0x12345678; + /* U N I X */ + + /* + BE_ORDER: 12 34 56 78 + U N I X + + LE_ORDER: 78 56 34 12 + X I N U + + PDP_ORDER: 34 12 78 56 + N U X I + */ + + if ( *(char *)&i == 0x12 ) + return BIG_ENDIAN; + else if ( *(char *)&i == 0x78 ) + return LITTLE_ENDIAN; + else if ( *(char *)&i == 0x34 ) + return PDP_ENDIAN; + + return -1; +} + +struct align_test_char { char dummy; char test; }; +struct align_test_short { char dummy; short test; }; +struct align_test_int { char dummy; int test; }; +struct align_test_long { char dummy; long test; }; +struct align_test_longlong { char dummy; long long test; }; +struct align_test_float { char dummy; float test; }; +struct align_test_double { char dummy; double test; }; +struct align_test_longdouble { char dummy; long double test;}; +struct align_test_voidptr { char dummy; void *test; }; + +int main (void) +{ + int tmp = ((char) -1); + + printf ("char is signed type : %s - char is %s\n", (tmp < 0) ? "yes" : "no", (tmp < 0) ? "SIGNED" : "UNSIGNED"); + printf ("Type sizes and alignment within structures:\n"); + printf ("char : %d, packing offset: %d\n", (int) sizeof(char), (int) ((size_t) &((struct align_test_char *)0)->test)); + printf ("short : %d, packing offset: %d\n", (int) sizeof(short), (int) ((size_t) &((struct align_test_short *)0)->test)); + printf ("int : %d, packing offset: %d\n", (int) sizeof(int), (int) ((size_t) &((struct align_test_int *)0)->test)); + printf ("long : %d, packing offset: %d\n", (int) sizeof(long), (int) ((size_t) &((struct align_test_long *)0)->test)); + printf ("long long : %d, packing offset: %d\n", (int) sizeof(long long),(int) ((size_t) &((struct align_test_longlong *)0)->test)); + printf ("void *ptr : %d, packing offset: %d\n", (int) sizeof(void *), (int) ((size_t) &((struct align_test_voidptr *)0)->test)); + printf ("float : %d, packing offset: %d\n", (int) sizeof(float), (int) ((size_t) &((struct align_test_float *)0)->test)); + printf ("double : %d, packing offset: %d\n", (int) sizeof(double), (int) ((size_t) &((struct align_test_double *)0)->test)); + printf ("long double: %d, packing offset: %d\n", (int) sizeof(long double),(int)((size_t)&((struct align_test_longdouble *)0)->test)); + + printf ("ENDIANNESS (BYTE ORDER):\n"); + + tmp = DetectByteorder(); + printf ("Runtime detection : "); + switch (tmp) + { + case BIG_ENDIAN: + printf ("Big Endian"); + break; + case LITTLE_ENDIAN: + printf ("Little Endian"); + break; + case PDP_ENDIAN: + printf ("PDP (NUXI) Endian"); + break; + default: + printf ("Unknown Endian"); + break; + } + printf ("\n"); + + tmp = COMPILED_BYTEORDER; + printf ("Compile time detection: "); + switch (tmp) + { + case BIG_ENDIAN: + printf ("Big Endian"); + break; + case LITTLE_ENDIAN: + printf ("Little Endian"); + break; + case PDP_ENDIAN: + printf ("PDP (NUXI) Endian"); + break; + default: + printf ("Unknown Endian"); + break; + } +#if defined(ENDIAN_GUESSED_SAFE) + printf (" (Safe guess)"); +#elif defined(ENDIAN_ASSUMED_UNSAFE) + printf (" (Unsafe assumption)"); +#endif + printf ("\n"); + + return 0; +} + diff --git a/Quake/anorm_dots.hpp b/Quake/anorm_dots.hpp new file mode 100644 index 0000000..21e6853 --- /dev/null +++ b/Quake/anorm_dots.hpp @@ -0,0 +1,580 @@ +/* + * anorm_dots.h + * + * Copyright (C) 1996-1997 Id Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// 0 + { 1.23, 1.30, 1.47, 1.35, 1.56, 1.71, 1.37, 1.38, + 1.59, 1.60, 1.79, 1.97, 1.88, 1.92, 1.79, 1.02, + 0.93, 1.07, 0.82, 0.87, 0.88, 0.94, 0.96, 1.14, + 1.11, 0.82, 0.83, 0.89, 0.89, 0.86, 0.94, 0.91, + 1.00, 1.21, 0.98, 1.48, 1.30, 1.57, 0.96, 1.07, + 1.14, 1.60, 1.61, 1.40, 1.37, 1.72, 1.78, 1.79, + 1.93, 1.99, 1.90, 1.68, 1.71, 1.86, 1.60, 1.68, + 1.78, 1.86, 1.93, 1.99, 1.97, 1.44, 1.22, 1.49, + 0.93, 0.99, 0.99, 1.23, 1.22, 1.44, 1.49, 0.89, + 0.89, 0.97, 0.91, 0.98, 1.19, 0.82, 0.76, 0.82, + 0.71, 0.72, 0.73, 0.76, 0.79, 0.86, 0.83, 0.72, + 0.76, 0.76, 0.89, 0.82, 0.89, 0.82, 0.89, 0.91, + 0.83, 0.96, 1.14, 0.97, 1.40, 1.19, 0.98, 0.94, + 1.00, 1.07, 1.37, 1.21, 1.48, 1.30, 1.57, 1.61, + 1.37, 0.86, 0.83, 0.91, 0.82, 0.82, 0.88, 0.89, + 0.96, 1.14, 0.98, 0.87, 0.93, 0.94, 1.02, 1.30, + 1.07, 1.35, 1.38, 1.11, 1.56, 1.92, 1.79, 1.79, + 1.59, 1.60, 1.72, 1.90, 1.79, 0.80, 0.85, 0.79, + 0.93, 0.80, 0.85, 0.77, 0.74, 0.72, 0.77, 0.74, + 0.72, 0.70, 0.70, 0.71, 0.76, 0.73, 0.79, 0.79, + 0.73, 0.76, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 1 + { 1.26, 1.26, 1.48, 1.23, 1.50, 1.71, 1.14, 1.19, + 1.38, 1.46, 1.64, 1.94, 1.87, 1.84, 1.71, 1.02, + 0.92, 1.00, 0.79, 0.85, 0.84, 0.91, 0.90, 0.98, + 0.99, 0.77, 0.77, 0.83, 0.82, 0.79, 0.86, 0.84, + 0.92, 0.99, 0.91, 1.24, 1.03, 1.33, 0.88, 0.94, + 0.97, 1.41, 1.39, 1.18, 1.11, 1.51, 1.61, 1.59, + 1.80, 1.91, 1.76, 1.54, 1.65, 1.76, 1.70, 1.70, + 1.85, 1.85, 1.97, 1.99, 1.93, 1.28, 1.09, 1.39, + 0.92, 0.97, 0.99, 1.18, 1.26, 1.52, 1.48, 0.83, + 0.85, 0.90, 0.88, 0.93, 1.00, 0.77, 0.73, 0.78, + 0.72, 0.71, 0.74, 0.75, 0.79, 0.86, 0.81, 0.75, + 0.81, 0.79, 0.96, 0.88, 0.94, 0.86, 0.93, 0.92, + 0.85, 1.08, 1.33, 1.05, 1.55, 1.31, 1.01, 1.05, + 1.27, 1.31, 1.60, 1.47, 1.70, 1.54, 1.76, 1.76, + 1.57, 0.93, 0.90, 0.99, 0.88, 0.88, 0.95, 0.97, + 1.11, 1.39, 1.20, 0.92, 0.97, 1.01, 1.10, 1.39, + 1.22, 1.51, 1.58, 1.32, 1.64, 1.97, 1.85, 1.91, + 1.77, 1.74, 1.88, 1.99, 1.91, 0.79, 0.86, 0.80, + 0.94, 0.84, 0.88, 0.74, 0.74, 0.71, 0.82, 0.77, + 0.76, 0.70, 0.73, 0.72, 0.73, 0.70, 0.74, 0.85, + 0.77, 0.82, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 2 + { 1.34, 1.27, 1.53, 1.17, 1.46, 1.71, 0.98, 1.05, + 1.20, 1.34, 1.48, 1.86, 1.82, 1.71, 1.62, 1.09, + 0.94, 0.99, 0.79, 0.85, 0.82, 0.90, 0.87, 0.93, + 0.96, 0.76, 0.74, 0.79, 0.76, 0.74, 0.79, 0.78, + 0.85, 0.92, 0.85, 1.00, 0.93, 1.06, 0.81, 0.86, + 0.89, 1.16, 1.12, 0.97, 0.95, 1.28, 1.38, 1.35, + 1.60, 1.77, 1.57, 1.33, 1.50, 1.58, 1.69, 1.63, + 1.82, 1.74, 1.91, 1.92, 1.80, 1.04, 0.97, 1.21, + 0.90, 0.93, 0.97, 1.05, 1.21, 1.48, 1.37, 0.77, + 0.80, 0.84, 0.85, 0.88, 0.92, 0.73, 0.71, 0.74, + 0.74, 0.71, 0.75, 0.73, 0.79, 0.84, 0.78, 0.79, + 0.86, 0.81, 1.05, 0.94, 0.99, 0.90, 0.95, 0.92, + 0.86, 1.24, 1.44, 1.14, 1.59, 1.34, 1.02, 1.27, + 1.50, 1.49, 1.80, 1.69, 1.86, 1.72, 1.87, 1.80, + 1.69, 1.00, 0.98, 1.23, 0.95, 0.96, 1.09, 1.16, + 1.37, 1.63, 1.46, 0.99, 1.10, 1.25, 1.24, 1.51, + 1.41, 1.67, 1.77, 1.55, 1.72, 1.95, 1.89, 1.98, + 1.91, 1.86, 1.97, 1.99, 1.94, 0.81, 0.89, 0.85, + 0.98, 0.90, 0.94, 0.75, 0.78, 0.73, 0.89, 0.83, + 0.82, 0.72, 0.77, 0.76, 0.72, 0.70, 0.71, 0.91, + 0.83, 0.89, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 3 + { 1.46, 1.34, 1.60, 1.16, 1.46, 1.71, 0.94, 0.99, + 1.05, 1.26, 1.33, 1.74, 1.76, 1.57, 1.54, 1.23, + 0.98, 1.05, 0.83, 0.89, 0.84, 0.92, 0.87, 0.91, + 0.96, 0.78, 0.74, 0.79, 0.72, 0.72, 0.75, 0.76, + 0.80, 0.88, 0.83, 0.94, 0.87, 0.95, 0.76, 0.80, + 0.82, 0.97, 0.96, 0.89, 0.88, 1.08, 1.11, 1.10, + 1.37, 1.59, 1.37, 1.07, 1.27, 1.34, 1.57, 1.45, + 1.69, 1.55, 1.77, 1.79, 1.60, 0.93, 0.90, 0.99, + 0.86, 0.87, 0.93, 0.96, 1.07, 1.35, 1.18, 0.73, + 0.76, 0.77, 0.81, 0.82, 0.85, 0.70, 0.71, 0.72, + 0.78, 0.73, 0.77, 0.73, 0.79, 0.82, 0.76, 0.83, + 0.90, 0.84, 1.18, 0.98, 1.03, 0.92, 0.95, 0.90, + 0.86, 1.32, 1.45, 1.15, 1.53, 1.27, 0.99, 1.42, + 1.65, 1.58, 1.93, 1.83, 1.94, 1.81, 1.88, 1.74, + 1.70, 1.19, 1.17, 1.44, 1.11, 1.15, 1.36, 1.41, + 1.61, 1.81, 1.67, 1.22, 1.34, 1.50, 1.42, 1.65, + 1.61, 1.82, 1.91, 1.75, 1.80, 1.89, 1.89, 1.98, + 1.99, 1.94, 1.98, 1.92, 1.87, 0.86, 0.95, 0.92, + 1.14, 0.98, 1.03, 0.79, 0.84, 0.77, 0.97, 0.90, + 0.89, 0.76, 0.82, 0.82, 0.74, 0.72, 0.71, 0.98, + 0.89, 0.97, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 4 + { 1.60, 1.44, 1.68, 1.22, 1.49, 1.71, 0.93, 0.99, + 0.99, 1.23, 1.22, 1.60, 1.68, 1.44, 1.49, 1.40, + 1.14, 1.19, 0.89, 0.96, 0.89, 0.97, 0.89, 0.91, + 0.98, 0.82, 0.76, 0.82, 0.71, 0.72, 0.73, 0.76, + 0.79, 0.86, 0.83, 0.91, 0.83, 0.89, 0.72, 0.76, + 0.76, 0.89, 0.89, 0.82, 0.82, 0.98, 0.96, 0.97, + 1.14, 1.40, 1.19, 0.94, 1.00, 1.07, 1.37, 1.21, + 1.48, 1.30, 1.57, 1.61, 1.37, 0.86, 0.83, 0.91, + 0.82, 0.82, 0.88, 0.89, 0.96, 1.14, 0.98, 0.70, + 0.72, 0.73, 0.77, 0.76, 0.79, 0.70, 0.72, 0.71, + 0.82, 0.77, 0.80, 0.74, 0.79, 0.80, 0.74, 0.87, + 0.93, 0.85, 1.23, 1.02, 1.02, 0.93, 0.93, 0.87, + 0.85, 1.30, 1.35, 1.07, 1.38, 1.11, 0.94, 1.47, + 1.71, 1.56, 1.97, 1.88, 1.92, 1.79, 1.79, 1.59, + 1.60, 1.30, 1.35, 1.56, 1.37, 1.38, 1.59, 1.60, + 1.79, 1.92, 1.79, 1.48, 1.57, 1.72, 1.61, 1.78, + 1.79, 1.93, 1.99, 1.90, 1.86, 1.78, 1.86, 1.93, + 1.99, 1.97, 1.90, 1.79, 1.72, 0.94, 1.07, 1.00, + 1.37, 1.21, 1.30, 0.86, 0.91, 0.83, 1.14, 0.98, + 0.96, 0.82, 0.88, 0.89, 0.79, 0.76, 0.73, 1.07, + 0.94, 1.11, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 5 + { 1.74, 1.57, 1.76, 1.33, 1.54, 1.71, 0.94, 1.05, + 0.99, 1.26, 1.16, 1.46, 1.60, 1.34, 1.46, 1.59, + 1.37, 1.37, 0.97, 1.11, 0.96, 1.10, 0.95, 0.94, + 1.08, 0.89, 0.82, 0.88, 0.72, 0.76, 0.75, 0.80, + 0.80, 0.88, 0.87, 0.91, 0.83, 0.87, 0.72, 0.76, + 0.74, 0.83, 0.84, 0.78, 0.79, 0.96, 0.89, 0.92, + 0.98, 1.23, 1.05, 0.86, 0.92, 0.95, 1.11, 0.98, + 1.22, 1.03, 1.34, 1.42, 1.14, 0.79, 0.77, 0.84, + 0.78, 0.76, 0.82, 0.82, 0.89, 0.97, 0.90, 0.70, + 0.71, 0.71, 0.73, 0.72, 0.74, 0.73, 0.76, 0.72, + 0.86, 0.81, 0.82, 0.76, 0.79, 0.77, 0.73, 0.90, + 0.95, 0.86, 1.18, 1.03, 0.98, 0.92, 0.90, 0.83, + 0.84, 1.19, 1.17, 0.98, 1.15, 0.97, 0.89, 1.42, + 1.65, 1.44, 1.93, 1.83, 1.81, 1.67, 1.61, 1.36, + 1.41, 1.32, 1.45, 1.58, 1.57, 1.53, 1.74, 1.70, + 1.88, 1.94, 1.81, 1.69, 1.77, 1.87, 1.79, 1.89, + 1.92, 1.98, 1.99, 1.98, 1.89, 1.65, 1.80, 1.82, + 1.91, 1.94, 1.75, 1.61, 1.50, 1.07, 1.34, 1.27, + 1.60, 1.45, 1.55, 0.93, 0.99, 0.90, 1.35, 1.18, + 1.07, 0.87, 0.93, 0.96, 0.85, 0.82, 0.77, 1.15, + 0.99, 1.27, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 6 + { 1.86, 1.71, 1.82, 1.48, 1.62, 1.71, 0.98, 1.20, + 1.05, 1.34, 1.17, 1.34, 1.53, 1.27, 1.46, 1.77, + 1.60, 1.57, 1.16, 1.38, 1.12, 1.35, 1.06, 1.00, + 1.28, 0.97, 0.89, 0.95, 0.76, 0.81, 0.79, 0.86, + 0.85, 0.92, 0.93, 0.93, 0.85, 0.87, 0.74, 0.78, + 0.74, 0.79, 0.82, 0.76, 0.79, 0.96, 0.85, 0.90, + 0.94, 1.09, 0.99, 0.81, 0.85, 0.89, 0.95, 0.90, + 0.99, 0.94, 1.10, 1.24, 0.98, 0.75, 0.73, 0.78, + 0.74, 0.72, 0.77, 0.76, 0.82, 0.89, 0.83, 0.73, + 0.71, 0.71, 0.71, 0.70, 0.72, 0.77, 0.80, 0.74, + 0.90, 0.85, 0.84, 0.78, 0.79, 0.75, 0.73, 0.92, + 0.95, 0.86, 1.05, 0.99, 0.94, 0.90, 0.86, 0.79, + 0.81, 1.00, 0.98, 0.91, 0.96, 0.89, 0.83, 1.27, + 1.50, 1.23, 1.80, 1.69, 1.63, 1.46, 1.37, 1.09, + 1.16, 1.24, 1.44, 1.49, 1.69, 1.59, 1.80, 1.69, + 1.87, 1.86, 1.72, 1.82, 1.91, 1.94, 1.92, 1.95, + 1.99, 1.98, 1.91, 1.97, 1.89, 1.51, 1.72, 1.67, + 1.77, 1.86, 1.55, 1.41, 1.25, 1.33, 1.58, 1.50, + 1.80, 1.63, 1.74, 1.04, 1.21, 0.97, 1.48, 1.37, + 1.21, 0.93, 0.97, 1.05, 0.92, 0.88, 0.84, 1.14, + 1.02, 1.34, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 7 + { 1.94, 1.84, 1.87, 1.64, 1.71, 1.71, 1.14, 1.38, + 1.19, 1.46, 1.23, 1.26, 1.48, 1.26, 1.50, 1.91, + 1.80, 1.76, 1.41, 1.61, 1.39, 1.59, 1.33, 1.24, + 1.51, 1.18, 0.97, 1.11, 0.82, 0.88, 0.86, 0.94, + 0.92, 0.99, 1.03, 0.98, 0.91, 0.90, 0.79, 0.84, + 0.77, 0.79, 0.84, 0.77, 0.83, 0.99, 0.85, 0.91, + 0.92, 1.02, 1.00, 0.79, 0.80, 0.86, 0.88, 0.84, + 0.92, 0.88, 0.97, 1.10, 0.94, 0.74, 0.71, 0.74, + 0.72, 0.70, 0.73, 0.72, 0.76, 0.82, 0.77, 0.77, + 0.73, 0.74, 0.71, 0.70, 0.73, 0.83, 0.85, 0.78, + 0.92, 0.88, 0.86, 0.81, 0.79, 0.74, 0.75, 0.92, + 0.93, 0.85, 0.96, 0.94, 0.88, 0.86, 0.81, 0.75, + 0.79, 0.93, 0.90, 0.85, 0.88, 0.82, 0.77, 1.05, + 1.27, 0.99, 1.60, 1.47, 1.39, 1.20, 1.11, 0.95, + 0.97, 1.08, 1.33, 1.31, 1.70, 1.55, 1.76, 1.57, + 1.76, 1.70, 1.54, 1.85, 1.97, 1.91, 1.99, 1.97, + 1.99, 1.91, 1.77, 1.88, 1.85, 1.39, 1.64, 1.51, + 1.58, 1.74, 1.32, 1.22, 1.01, 1.54, 1.76, 1.65, + 1.93, 1.70, 1.85, 1.28, 1.39, 1.09, 1.52, 1.48, + 1.26, 0.97, 0.99, 1.18, 1.00, 0.93, 0.90, 1.05, + 1.01, 1.31, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 8 + { 1.97, 1.92, 1.88, 1.79, 1.79, 1.71, 1.37, 1.59, + 1.38, 1.60, 1.35, 1.23, 1.47, 1.30, 1.56, 1.99, + 1.93, 1.90, 1.60, 1.78, 1.61, 1.79, 1.57, 1.48, + 1.72, 1.40, 1.14, 1.37, 0.89, 0.96, 0.94, 1.07, + 1.00, 1.21, 1.30, 1.14, 0.98, 0.96, 0.86, 0.91, + 0.83, 0.82, 0.88, 0.82, 0.89, 1.11, 0.87, 0.94, + 0.93, 1.02, 1.07, 0.80, 0.79, 0.85, 0.82, 0.80, + 0.87, 0.85, 0.93, 1.02, 0.93, 0.77, 0.72, 0.74, + 0.71, 0.70, 0.70, 0.71, 0.72, 0.77, 0.74, 0.82, + 0.76, 0.79, 0.72, 0.73, 0.76, 0.89, 0.89, 0.82, + 0.93, 0.91, 0.86, 0.83, 0.79, 0.73, 0.76, 0.91, + 0.89, 0.83, 0.89, 0.89, 0.82, 0.82, 0.76, 0.72, + 0.76, 0.86, 0.83, 0.79, 0.82, 0.76, 0.73, 0.94, + 1.00, 0.91, 1.37, 1.21, 1.14, 0.98, 0.96, 0.88, + 0.89, 0.96, 1.14, 1.07, 1.60, 1.40, 1.61, 1.37, + 1.57, 1.48, 1.30, 1.78, 1.93, 1.79, 1.99, 1.92, + 1.90, 1.79, 1.59, 1.72, 1.79, 1.30, 1.56, 1.35, + 1.38, 1.60, 1.11, 1.07, 0.94, 1.68, 1.86, 1.71, + 1.97, 1.68, 1.86, 1.44, 1.49, 1.22, 1.44, 1.49, + 1.22, 0.99, 0.99, 1.23, 1.19, 0.98, 0.97, 0.97, + 0.98, 1.19, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 9 + { 1.94, 1.97, 1.87, 1.91, 1.85, 1.71, 1.60, 1.77, + 1.58, 1.74, 1.51, 1.26, 1.48, 1.39, 1.64, 1.99, + 1.97, 1.99, 1.70, 1.85, 1.76, 1.91, 1.76, 1.70, + 1.88, 1.55, 1.33, 1.57, 0.96, 1.08, 1.05, 1.31, + 1.27, 1.47, 1.54, 1.39, 1.20, 1.11, 0.93, 0.99, + 0.90, 0.88, 0.95, 0.88, 0.97, 1.32, 0.92, 1.01, + 0.97, 1.10, 1.22, 0.84, 0.80, 0.88, 0.79, 0.79, + 0.85, 0.86, 0.92, 1.02, 0.94, 0.82, 0.76, 0.77, + 0.72, 0.73, 0.70, 0.72, 0.71, 0.74, 0.74, 0.88, + 0.81, 0.85, 0.75, 0.77, 0.82, 0.94, 0.93, 0.86, + 0.92, 0.92, 0.86, 0.85, 0.79, 0.74, 0.79, 0.88, + 0.85, 0.81, 0.82, 0.83, 0.77, 0.78, 0.73, 0.71, + 0.75, 0.79, 0.77, 0.74, 0.77, 0.73, 0.70, 0.86, + 0.92, 0.84, 1.14, 0.99, 0.98, 0.91, 0.90, 0.84, + 0.83, 0.88, 0.97, 0.94, 1.41, 1.18, 1.39, 1.11, + 1.33, 1.24, 1.03, 1.61, 1.80, 1.59, 1.91, 1.84, + 1.76, 1.64, 1.38, 1.51, 1.71, 1.26, 1.50, 1.23, + 1.19, 1.46, 0.99, 1.00, 0.91, 1.70, 1.85, 1.65, + 1.93, 1.54, 1.76, 1.52, 1.48, 1.26, 1.28, 1.39, + 1.09, 0.99, 0.97, 1.18, 1.31, 1.01, 1.05, 0.90, + 0.93, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 10 + { 1.86, 1.95, 1.82, 1.98, 1.89, 1.71, 1.80, 1.91, + 1.77, 1.86, 1.67, 1.34, 1.53, 1.51, 1.72, 1.92, + 1.91, 1.99, 1.69, 1.82, 1.80, 1.94, 1.87, 1.86, + 1.97, 1.59, 1.44, 1.69, 1.05, 1.24, 1.27, 1.49, + 1.50, 1.69, 1.72, 1.63, 1.46, 1.37, 1.00, 1.23, + 0.98, 0.95, 1.09, 0.96, 1.16, 1.55, 0.99, 1.25, + 1.10, 1.24, 1.41, 0.90, 0.85, 0.94, 0.79, 0.81, + 0.85, 0.89, 0.94, 1.09, 0.98, 0.89, 0.82, 0.83, + 0.74, 0.77, 0.72, 0.76, 0.73, 0.75, 0.78, 0.94, + 0.86, 0.91, 0.79, 0.83, 0.89, 0.99, 0.95, 0.90, + 0.90, 0.92, 0.84, 0.86, 0.79, 0.75, 0.81, 0.85, + 0.80, 0.78, 0.76, 0.77, 0.73, 0.74, 0.71, 0.71, + 0.73, 0.74, 0.74, 0.71, 0.76, 0.72, 0.70, 0.79, + 0.85, 0.78, 0.98, 0.92, 0.93, 0.85, 0.87, 0.82, + 0.79, 0.81, 0.89, 0.86, 1.16, 0.97, 1.12, 0.95, + 1.06, 1.00, 0.93, 1.38, 1.60, 1.35, 1.77, 1.71, + 1.57, 1.48, 1.20, 1.28, 1.62, 1.27, 1.46, 1.17, + 1.05, 1.34, 0.96, 0.99, 0.90, 1.63, 1.74, 1.50, + 1.80, 1.33, 1.58, 1.48, 1.37, 1.21, 1.04, 1.21, + 0.97, 0.97, 0.93, 1.05, 1.34, 1.02, 1.14, 0.84, + 0.88, 0.92, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 11 + { 1.74, 1.89, 1.76, 1.98, 1.89, 1.71, 1.93, 1.99, + 1.91, 1.94, 1.82, 1.46, 1.60, 1.65, 1.80, 1.79, + 1.77, 1.92, 1.57, 1.69, 1.74, 1.87, 1.88, 1.94, + 1.98, 1.53, 1.45, 1.70, 1.18, 1.32, 1.42, 1.58, + 1.65, 1.83, 1.81, 1.81, 1.67, 1.61, 1.19, 1.44, + 1.17, 1.11, 1.36, 1.15, 1.41, 1.75, 1.22, 1.50, + 1.34, 1.42, 1.61, 0.98, 0.92, 1.03, 0.83, 0.86, + 0.89, 0.95, 0.98, 1.23, 1.14, 0.97, 0.89, 0.90, + 0.78, 0.82, 0.76, 0.82, 0.77, 0.79, 0.84, 0.98, + 0.90, 0.98, 0.83, 0.89, 0.97, 1.03, 0.95, 0.92, + 0.86, 0.90, 0.82, 0.86, 0.79, 0.77, 0.84, 0.81, + 0.76, 0.76, 0.72, 0.73, 0.70, 0.72, 0.71, 0.73, + 0.73, 0.72, 0.74, 0.71, 0.78, 0.74, 0.72, 0.75, + 0.80, 0.76, 0.94, 0.88, 0.91, 0.83, 0.87, 0.84, + 0.79, 0.76, 0.82, 0.80, 0.97, 0.89, 0.96, 0.88, + 0.95, 0.94, 0.87, 1.11, 1.37, 1.10, 1.59, 1.57, + 1.37, 1.33, 1.05, 1.08, 1.54, 1.34, 1.46, 1.16, + 0.99, 1.26, 0.96, 1.05, 0.92, 1.45, 1.55, 1.27, + 1.60, 1.07, 1.34, 1.35, 1.18, 1.07, 0.93, 0.99, + 0.90, 0.93, 0.87, 0.96, 1.27, 0.99, 1.15, 0.77, + 0.82, 0.85, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 12 + { 1.60, 1.78, 1.68, 1.93, 1.86, 1.71, 1.97, 1.99, + 1.99, 1.97, 1.93, 1.60, 1.68, 1.78, 1.86, 1.61, + 1.57, 1.79, 1.37, 1.48, 1.59, 1.72, 1.79, 1.92, + 1.90, 1.38, 1.35, 1.60, 1.23, 1.30, 1.47, 1.56, + 1.71, 1.88, 1.79, 1.92, 1.79, 1.79, 1.30, 1.56, + 1.35, 1.37, 1.59, 1.38, 1.60, 1.90, 1.48, 1.72, + 1.57, 1.61, 1.79, 1.21, 1.00, 1.30, 0.89, 0.94, + 0.96, 1.07, 1.14, 1.40, 1.37, 1.14, 0.96, 0.98, + 0.82, 0.88, 0.82, 0.89, 0.83, 0.86, 0.91, 1.02, + 0.93, 1.07, 0.87, 0.94, 1.11, 1.02, 0.93, 0.93, + 0.82, 0.87, 0.80, 0.85, 0.79, 0.80, 0.85, 0.77, + 0.72, 0.74, 0.71, 0.70, 0.70, 0.71, 0.72, 0.77, + 0.74, 0.72, 0.76, 0.73, 0.82, 0.79, 0.76, 0.73, + 0.79, 0.76, 0.93, 0.86, 0.91, 0.83, 0.89, 0.89, + 0.82, 0.72, 0.76, 0.76, 0.89, 0.82, 0.89, 0.82, + 0.89, 0.91, 0.83, 0.96, 1.14, 0.97, 1.40, 1.44, + 1.19, 1.22, 0.99, 0.98, 1.49, 1.44, 1.49, 1.22, + 0.99, 1.23, 0.98, 1.19, 0.97, 1.21, 1.30, 1.00, + 1.37, 0.94, 1.07, 1.14, 0.98, 0.96, 0.86, 0.91, + 0.83, 0.88, 0.82, 0.89, 1.11, 0.94, 1.07, 0.73, + 0.76, 0.79, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 13 + { 1.46, 1.65, 1.60, 1.82, 1.80, 1.71, 1.93, 1.91, + 1.99, 1.94, 1.98, 1.74, 1.76, 1.89, 1.89, 1.42, + 1.34, 1.61, 1.11, 1.22, 1.36, 1.50, 1.61, 1.81, + 1.75, 1.15, 1.17, 1.41, 1.18, 1.19, 1.42, 1.44, + 1.65, 1.83, 1.67, 1.94, 1.81, 1.88, 1.32, 1.58, + 1.45, 1.57, 1.74, 1.53, 1.70, 1.98, 1.69, 1.87, + 1.77, 1.79, 1.92, 1.45, 1.27, 1.55, 0.97, 1.07, + 1.11, 1.34, 1.37, 1.59, 1.60, 1.35, 1.07, 1.18, + 0.86, 0.93, 0.87, 0.96, 0.90, 0.93, 0.99, 1.03, + 0.95, 1.15, 0.90, 0.99, 1.27, 0.98, 0.90, 0.92, + 0.78, 0.83, 0.77, 0.84, 0.79, 0.82, 0.86, 0.73, + 0.71, 0.73, 0.72, 0.70, 0.73, 0.72, 0.76, 0.81, + 0.76, 0.76, 0.82, 0.77, 0.89, 0.85, 0.82, 0.75, + 0.80, 0.80, 0.94, 0.88, 0.94, 0.87, 0.95, 0.96, + 0.88, 0.72, 0.74, 0.76, 0.83, 0.78, 0.84, 0.79, + 0.87, 0.91, 0.83, 0.89, 0.98, 0.92, 1.23, 1.34, + 1.05, 1.16, 0.99, 0.96, 1.46, 1.57, 1.54, 1.33, + 1.05, 1.26, 1.08, 1.37, 1.10, 0.98, 1.03, 0.92, + 1.14, 0.86, 0.95, 0.97, 0.90, 0.89, 0.79, 0.84, + 0.77, 0.82, 0.76, 0.82, 0.97, 0.89, 0.98, 0.71, + 0.72, 0.74, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 14 + { 1.34, 1.51, 1.53, 1.67, 1.72, 1.71, 1.80, 1.77, + 1.91, 1.86, 1.98, 1.86, 1.82, 1.95, 1.89, 1.24, + 1.10, 1.41, 0.95, 0.99, 1.09, 1.25, 1.37, 1.63, + 1.55, 0.96, 0.98, 1.16, 1.05, 1.00, 1.27, 1.23, + 1.50, 1.69, 1.46, 1.86, 1.72, 1.87, 1.24, 1.49, + 1.44, 1.69, 1.80, 1.59, 1.69, 1.97, 1.82, 1.94, + 1.91, 1.92, 1.99, 1.63, 1.50, 1.74, 1.16, 1.33, + 1.38, 1.58, 1.60, 1.77, 1.80, 1.48, 1.21, 1.37, + 0.90, 0.97, 0.93, 1.05, 0.97, 1.04, 1.21, 0.99, + 0.95, 1.14, 0.92, 1.02, 1.34, 0.94, 0.86, 0.90, + 0.74, 0.79, 0.75, 0.81, 0.79, 0.84, 0.86, 0.71, + 0.71, 0.73, 0.76, 0.73, 0.77, 0.74, 0.80, 0.85, + 0.78, 0.81, 0.89, 0.84, 0.97, 0.92, 0.88, 0.79, + 0.85, 0.86, 0.98, 0.92, 1.00, 0.93, 1.06, 1.12, + 0.95, 0.74, 0.74, 0.78, 0.79, 0.76, 0.82, 0.79, + 0.87, 0.93, 0.85, 0.85, 0.94, 0.90, 1.09, 1.27, + 0.99, 1.17, 1.05, 0.96, 1.46, 1.71, 1.62, 1.48, + 1.20, 1.34, 1.28, 1.57, 1.35, 0.90, 0.94, 0.85, + 0.98, 0.81, 0.89, 0.89, 0.83, 0.82, 0.75, 0.78, + 0.73, 0.77, 0.72, 0.76, 0.89, 0.83, 0.91, 0.71, + 0.70, 0.72, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + }, + +// 15 + { 1.26, 1.39, 1.48, 1.51, 1.64, 1.71, 1.60, 1.58, + 1.77, 1.74, 1.91, 1.94, 1.87, 1.97, 1.85, 1.10, + 0.97, 1.22, 0.88, 0.92, 0.95, 1.01, 1.11, 1.39, + 1.32, 0.88, 0.90, 0.97, 0.96, 0.93, 1.05, 0.99, + 1.27, 1.47, 1.20, 1.70, 1.54, 1.76, 1.08, 1.31, + 1.33, 1.70, 1.76, 1.55, 1.57, 1.88, 1.85, 1.91, + 1.97, 1.99, 1.99, 1.70, 1.65, 1.85, 1.41, 1.54, + 1.61, 1.76, 1.80, 1.91, 1.93, 1.52, 1.26, 1.48, + 0.92, 0.99, 0.97, 1.18, 1.09, 1.28, 1.39, 0.94, + 0.93, 1.05, 0.92, 1.01, 1.31, 0.88, 0.81, 0.86, + 0.72, 0.75, 0.74, 0.79, 0.79, 0.86, 0.85, 0.71, + 0.73, 0.75, 0.82, 0.77, 0.83, 0.78, 0.85, 0.88, + 0.81, 0.88, 0.97, 0.90, 1.18, 1.00, 0.93, 0.86, + 0.92, 0.94, 1.14, 0.99, 1.24, 1.03, 1.33, 1.39, + 1.11, 0.79, 0.77, 0.84, 0.79, 0.77, 0.84, 0.83, + 0.90, 0.98, 0.91, 0.85, 0.92, 0.91, 1.02, 1.26, + 1.00, 1.23, 1.19, 0.99, 1.50, 1.84, 1.71, 1.64, + 1.38, 1.46, 1.51, 1.76, 1.59, 0.84, 0.88, 0.80, + 0.94, 0.79, 0.86, 0.82, 0.77, 0.76, 0.74, 0.74, + 0.71, 0.73, 0.70, 0.72, 0.82, 0.77, 0.85, 0.74, + 0.70, 0.73, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, + 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 + } + diff --git a/Quake/anorms.hpp b/Quake/anorms.hpp new file mode 100644 index 0000000..35a903e --- /dev/null +++ b/Quake/anorms.hpp @@ -0,0 +1,182 @@ +/* + * anorms.h + * + * Copyright (C) 1996-1997 Id Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + { -0.525731, 0.000000, 0.850651 }, + { -0.442863, 0.238856, 0.864188 }, + { -0.295242, 0.000000, 0.955423 }, + { -0.309017, 0.500000, 0.809017 }, + { -0.162460, 0.262866, 0.951056 }, + { 0.000000, 0.000000, 1.000000 }, + { 0.000000, 0.850651, 0.525731 }, + { -0.147621, 0.716567, 0.681718 }, + { 0.147621, 0.716567, 0.681718 }, + { 0.000000, 0.525731, 0.850651 }, + { 0.309017, 0.500000, 0.809017 }, + { 0.525731, 0.000000, 0.850651 }, + { 0.295242, 0.000000, 0.955423 }, + { 0.442863, 0.238856, 0.864188 }, + { 0.162460, 0.262866, 0.951056 }, + { -0.681718, 0.147621, 0.716567 }, + { -0.809017, 0.309017, 0.500000 }, + { -0.587785, 0.425325, 0.688191 }, + { -0.850651, 0.525731, 0.000000 }, + { -0.864188, 0.442863, 0.238856 }, + { -0.716567, 0.681718, 0.147621 }, + { -0.688191, 0.587785, 0.425325 }, + { -0.500000, 0.809017, 0.309017 }, + { -0.238856, 0.864188, 0.442863 }, + { -0.425325, 0.688191, 0.587785 }, + { -0.716567, 0.681718, -0.147621 }, + { -0.500000, 0.809017, -0.309017 }, + { -0.525731, 0.850651, 0.000000 }, + { 0.000000, 0.850651, -0.525731 }, + { -0.238856, 0.864188, -0.442863 }, + { 0.000000, 0.955423, -0.295242 }, + { -0.262866, 0.951056, -0.162460 }, + { 0.000000, 1.000000, 0.000000 }, + { 0.000000, 0.955423, 0.295242 }, + { -0.262866, 0.951056, 0.162460 }, + { 0.238856, 0.864188, 0.442863 }, + { 0.262866, 0.951056, 0.162460 }, + { 0.500000, 0.809017, 0.309017 }, + { 0.238856, 0.864188, -0.442863 }, + { 0.262866, 0.951056, -0.162460 }, + { 0.500000, 0.809017, -0.309017 }, + { 0.850651, 0.525731, 0.000000 }, + { 0.716567, 0.681718, 0.147621 }, + { 0.716567, 0.681718, -0.147621 }, + { 0.525731, 0.850651, 0.000000 }, + { 0.425325, 0.688191, 0.587785 }, + { 0.864188, 0.442863, 0.238856 }, + { 0.688191, 0.587785, 0.425325 }, + { 0.809017, 0.309017, 0.500000 }, + { 0.681718, 0.147621, 0.716567 }, + { 0.587785, 0.425325, 0.688191 }, + { 0.955423, 0.295242, 0.000000 }, + { 1.000000, 0.000000, 0.000000 }, + { 0.951056, 0.162460, 0.262866 }, + { 0.850651, -0.525731, 0.000000 }, + { 0.955423, -0.295242, 0.000000 }, + { 0.864188, -0.442863, 0.238856 }, + { 0.951056, -0.162460, 0.262866 }, + { 0.809017, -0.309017, 0.500000 }, + { 0.681718, -0.147621, 0.716567 }, + { 0.850651, 0.000000, 0.525731 }, + { 0.864188, 0.442863, -0.238856 }, + { 0.809017, 0.309017, -0.500000 }, + { 0.951056, 0.162460, -0.262866 }, + { 0.525731, 0.000000, -0.850651 }, + { 0.681718, 0.147621, -0.716567 }, + { 0.681718, -0.147621, -0.716567 }, + { 0.850651, 0.000000, -0.525731 }, + { 0.809017, -0.309017, -0.500000 }, + { 0.864188, -0.442863, -0.238856 }, + { 0.951056, -0.162460, -0.262866 }, + { 0.147621, 0.716567, -0.681718 }, + { 0.309017, 0.500000, -0.809017 }, + { 0.425325, 0.688191, -0.587785 }, + { 0.442863, 0.238856, -0.864188 }, + { 0.587785, 0.425325, -0.688191 }, + { 0.688191, 0.587785, -0.425325 }, + { -0.147621, 0.716567, -0.681718 }, + { -0.309017, 0.500000, -0.809017 }, + { 0.000000, 0.525731, -0.850651 }, + { -0.525731, 0.000000, -0.850651 }, + { -0.442863, 0.238856, -0.864188 }, + { -0.295242, 0.000000, -0.955423 }, + { -0.162460, 0.262866, -0.951056 }, + { 0.000000, 0.000000, -1.000000 }, + { 0.295242, 0.000000, -0.955423 }, + { 0.162460, 0.262866, -0.951056 }, + { -0.442863, -0.238856, -0.864188 }, + { -0.309017, -0.500000, -0.809017 }, + { -0.162460, -0.262866, -0.951056 }, + { 0.000000, -0.850651, -0.525731 }, + { -0.147621, -0.716567, -0.681718 }, + { 0.147621, -0.716567, -0.681718 }, + { 0.000000, -0.525731, -0.850651 }, + { 0.309017, -0.500000, -0.809017 }, + { 0.442863, -0.238856, -0.864188 }, + { 0.162460, -0.262866, -0.951056 }, + { 0.238856, -0.864188, -0.442863 }, + { 0.500000, -0.809017, -0.309017 }, + { 0.425325, -0.688191, -0.587785 }, + { 0.716567, -0.681718, -0.147621 }, + { 0.688191, -0.587785, -0.425325 }, + { 0.587785, -0.425325, -0.688191 }, + { 0.000000, -0.955423, -0.295242 }, + { 0.000000, -1.000000, 0.000000 }, + { 0.262866, -0.951056, -0.162460 }, + { 0.000000, -0.850651, 0.525731 }, + { 0.000000, -0.955423, 0.295242 }, + { 0.238856, -0.864188, 0.442863 }, + { 0.262866, -0.951056, 0.162460 }, + { 0.500000, -0.809017, 0.309017 }, + { 0.716567, -0.681718, 0.147621 }, + { 0.525731, -0.850651, 0.000000 }, + { -0.238856, -0.864188, -0.442863 }, + { -0.500000, -0.809017, -0.309017 }, + { -0.262866, -0.951056, -0.162460 }, + { -0.850651, -0.525731, 0.000000 }, + { -0.716567, -0.681718, -0.147621 }, + { -0.716567, -0.681718, 0.147621 }, + { -0.525731, -0.850651, 0.000000 }, + { -0.500000, -0.809017, 0.309017 }, + { -0.238856, -0.864188, 0.442863 }, + { -0.262866, -0.951056, 0.162460 }, + { -0.864188, -0.442863, 0.238856 }, + { -0.809017, -0.309017, 0.500000 }, + { -0.688191, -0.587785, 0.425325 }, + { -0.681718, -0.147621, 0.716567 }, + { -0.442863, -0.238856, 0.864188 }, + { -0.587785, -0.425325, 0.688191 }, + { -0.309017, -0.500000, 0.809017 }, + { -0.147621, -0.716567, 0.681718 }, + { -0.425325, -0.688191, 0.587785 }, + { -0.162460, -0.262866, 0.951056 }, + { 0.442863, -0.238856, 0.864188 }, + { 0.162460, -0.262866, 0.951056 }, + { 0.309017, -0.500000, 0.809017 }, + { 0.147621, -0.716567, 0.681718 }, + { 0.000000, -0.525731, 0.850651 }, + { 0.425325, -0.688191, 0.587785 }, + { 0.587785, -0.425325, 0.688191 }, + { 0.688191, -0.587785, 0.425325 }, + { -0.955423, 0.295242, 0.000000 }, + { -0.951056, 0.162460, 0.262866 }, + { -1.000000, 0.000000, 0.000000 }, + { -0.850651, 0.000000, 0.525731 }, + { -0.955423, -0.295242, 0.000000 }, + { -0.951056, -0.162460, 0.262866 }, + { -0.864188, 0.442863, -0.238856 }, + { -0.951056, 0.162460, -0.262866 }, + { -0.809017, 0.309017, -0.500000 }, + { -0.864188, -0.442863, -0.238856 }, + { -0.951056, -0.162460, -0.262866 }, + { -0.809017, -0.309017, -0.500000 }, + { -0.681718, 0.147621, -0.716567 }, + { -0.681718, -0.147621, -0.716567 }, + { -0.850651, 0.000000, -0.525731 }, + { -0.688191, 0.587785, -0.425325 }, + { -0.587785, 0.425325, -0.688191 }, + { -0.425325, 0.688191, -0.587785 }, + { -0.425325, -0.688191, -0.587785 }, + { -0.587785, -0.425325, -0.688191 }, + { -0.688191, -0.587785, -0.425325 }, diff --git a/Quake/arch_def.hpp b/Quake/arch_def.hpp new file mode 100644 index 0000000..8dd5b86 --- /dev/null +++ b/Quake/arch_def.hpp @@ -0,0 +1,178 @@ +/* + * arch_def.h + * platform specific definitions + * - standalone header + * - doesn't and must not include any other headers + * - shouldn't depend on compiler.h, q_stdinc.h, or + * any other headers + * + * Copyright (C) 2007-2016 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ARCHDEFS_H +#define ARCHDEFS_H + + +#if defined(__DJGPP__) || defined(__MSDOS__) || defined(__DOS__) || defined(_MSDOS) + +# if !defined(PLATFORM_DOS) +# define PLATFORM_DOS 1 +# endif + +#elif defined(__OS2__) || defined(__EMX__) + +# if !defined(PLATFORM_OS2) +# define PLATFORM_OS2 1 +# endif + +#elif defined(_WIN32) || defined(__WIN32__) || defined(_WIN64) || defined(__NT__) || defined(_Windows) + +# if !defined(PLATFORM_WINDOWS) +# define PLATFORM_WINDOWS 1 +# endif + +#elif defined(__APPLE__) && defined(__MACH__) /* Mac OS X */ + +# if !defined(PLATFORM_OSX) +# define PLATFORM_OSX 1 +# endif + +#elif defined(macintosh) /* Mac OS classic */ + +# if !defined(PLATFORM_MAC) +# define PLATFORM_MAC 1 +# endif + +#elif defined(__MORPHOS__) || defined(__AROS__) || defined(AMIGAOS) || \ + defined(__amigaos__) || defined(__amigaos4__) ||defined(__amigados__) || \ + defined(AMIGA) || defined(_AMIGA) || defined(__AMIGA__) + +# if !defined(PLATFORM_AMIGA) +# define PLATFORM_AMIGA 1 +# endif + +#elif defined(__riscos__) + +# if !defined(PLATFORM_RISCOS) +# define PLATFORM_RISCOS 1 +# endif + +#elif defined(__HAIKU__) + +# if !defined(PLATFORM_HAIKU) +# define PLATFORM_HAIKU 1 +# endif + +#else /* here goes the unix platforms */ + +#if defined(__unix) || defined(__unix__) || defined(unix) || \ + defined(__linux__) || defined(__linux) || \ + defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) /* Debian GNU/kFreeBSD */ || \ + defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__hpux) || defined(__hpux__) || defined(_hpux) || \ + defined(__sun) || defined(sun) || \ + defined(__sgi) || defined(sgi) || defined(__sgi__) || \ + defined(__GNU__) /* GNU/Hurd */ || \ + defined(__QNX__) || defined(__QNXNTO__) +# if !defined(PLATFORM_UNIX) +# define PLATFORM_UNIX 1 +# endif +#endif + +#endif /* PLATFORM_xxx */ + + +#if defined (PLATFORM_OSX) /* OS X is unix-based */ +# if !defined(PLATFORM_UNIX) +# define PLATFORM_UNIX 2 +# endif +#endif /* OS X -> PLATFORM_UNIX */ + + +#if defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) /* Debian GNU/kFreeBSD */ || \ + defined(__OpenBSD__) || defined(__NetBSD__) +# if !defined(PLATFORM_BSD) +# define PLATFORM_BSD 1 +# endif +#endif /* PLATFORM_BSD (for convenience) */ + + +#if defined(PLATFORM_AMIGA) && !defined(PLATFORM_AMIGAOS3) +# if !defined(__MORPHOS__) && !defined(__AROS__) && !defined(__amigaos4__) +# define PLATFORM_AMIGAOS3 1 +# endif +#endif /* PLATFORM_AMIGAOS3 (for convenience) */ + + +#if defined(_WIN64) +# define PLATFORM_STRING "Win64" +#elif defined(PLATFORM_WINDOWS) +# define PLATFORM_STRING "Windows" +#elif defined(PLATFORM_DOS) +# define PLATFORM_STRING "DOS" +#elif defined(PLATFORM_OS2) +# define PLATFORM_STRING "OS/2" +#elif defined(__linux__) || defined(__linux) +# define PLATFORM_STRING "Linux" +#elif defined(__DragonFly__) +# define PLATFORM_STRING "DragonFly" +#elif defined(__FreeBSD__) +# define PLATFORM_STRING "FreeBSD" +#elif defined(__NetBSD__) +# define PLATFORM_STRING "NetBSD" +#elif defined(__OpenBSD__) +# define PLATFORM_STRING "OpenBSD" +#elif defined(__MORPHOS__) +# define PLATFORM_STRING "MorphOS" +#elif defined(__AROS__) +# define PLATFORM_STRING "AROS" +#elif defined(__amigaos4__) +# define PLATFORM_STRING "AmigaOS4" +#elif defined(PLATFORM_AMIGA) +# define PLATFORM_STRING "AmigaOS" +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_STRING "QNX" +#elif defined(PLATFORM_OSX) +# define PLATFORM_STRING "MacOSX" +#elif defined(PLATFORM_MAC) +# define PLATFORM_STRING "MacOS" +#elif defined(__hpux) || defined(__hpux__) || defined(_hpux) +# define PLATFORM_STRING "HP-UX" +#elif (defined(__sun) || defined(sun)) && (defined(__svr4__) || defined(__SVR4)) +# define PLATFORM_STRING "Solaris" +#elif defined(__sun) || defined(sun) +# define PLATFORM_STRING "SunOS" +#elif defined(__sgi) || defined(sgi) || defined(__sgi__) +# define PLATFORM_STRING "Irix" +#elif defined(PLATFORM_RISCOS) +# define PLATFORM_STRING "RiscOS" +#elif defined(__GNU__) +# define PLATFORM_STRING "GNU/Hurd" +#elif defined(PLATFORM_HAIKU) +# define PLATFORM_STRING "Haiku" +#elif defined(PLATFORM_UNIX) +# define PLATFORM_STRING "Unix" +#else +# define PLATFORM_STRING "Unknown" +# warning "Platform is UNKNOWN." +#endif /* PLATFORM_STRING */ + +#endif /* ARCHDEFS_H */ + diff --git a/Quake/bgmusic.cpp b/Quake/bgmusic.cpp new file mode 100644 index 0000000..65902df --- /dev/null +++ b/Quake/bgmusic.cpp @@ -0,0 +1,478 @@ +/* + * Background music handling for Quakespasm (adapted from uHexen2) + * Handles streaming music as raw sound samples and runs the midi driver + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010-2018 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.hpp" +#include "snd_codec.hpp" +#include "bgmusic.hpp" + +#define MUSIC_DIRNAME "music" + +qboolean bgmloop; +cvar_t bgm_extmusic = {"bgm_extmusic", "1", CVAR_ARCHIVE}; + +static qboolean no_extmusic= false; +static float old_volume = -1.0f; + +typedef enum _bgm_player +{ + BGM_NONE = -1, + BGM_MIDIDRV = 1, + BGM_STREAMER +} bgm_player_t; + +typedef struct music_handler_s +{ + unsigned int type; /* 1U << n (see snd_codec.h) */ + bgm_player_t player; /* Enumerated bgm player type */ + int is_available; /* -1 means not present */ + const char *ext; /* Expected file extension */ + const char *dir; /* Where to look for music file */ + struct music_handler_s *next; +} music_handler_t; + +static music_handler_t wanted_handlers[] = +{ + { CODECTYPE_VORBIS,BGM_STREAMER,-1, "ogg", MUSIC_DIRNAME, NULL }, + { CODECTYPE_OPUS, BGM_STREAMER, -1, "opus", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MP3, BGM_STREAMER, -1, "mp3", MUSIC_DIRNAME, NULL }, + { CODECTYPE_FLAC, BGM_STREAMER, -1, "flac", MUSIC_DIRNAME, NULL }, + { CODECTYPE_WAV, BGM_STREAMER, -1, "wav", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "it", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "s3m", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "xm", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MOD, BGM_STREAMER, -1, "mod", MUSIC_DIRNAME, NULL }, + { CODECTYPE_UMX, BGM_STREAMER, -1, "umx", MUSIC_DIRNAME, NULL }, + { CODECTYPE_NONE, BGM_NONE, -1, NULL, NULL, NULL } +}; + +static music_handler_t *music_handlers = NULL; + +#define ANY_CODECTYPE 0xFFFFFFFF +#define CDRIP_TYPES (CODECTYPE_VORBIS | CODECTYPE_MP3 | CODECTYPE_FLAC | CODECTYPE_WAV | CODECTYPE_OPUS) +#define CDRIPTYPE(x) (((x) & CDRIP_TYPES) != 0) + +static snd_stream_t *bgmstream = NULL; + +static void BGM_Play_f (void) +{ + if (Cmd_Argc() == 2) { + BGM_Play (Cmd_Argv(1)); + } + else { + Con_Printf ("music \n"); + } +} + +static void BGM_Pause_f (void) +{ + BGM_Pause (); +} + +static void BGM_Resume_f (void) +{ + BGM_Resume (); +} + +static void BGM_Loop_f (void) +{ + if (Cmd_Argc() == 2) { + if (q_strcasecmp(Cmd_Argv(1), "0") == 0 || + q_strcasecmp(Cmd_Argv(1),"off") == 0) + bgmloop = false; + else if (q_strcasecmp(Cmd_Argv(1), "1") == 0 || + q_strcasecmp(Cmd_Argv(1),"on") == 0) + bgmloop = true; + else if (q_strcasecmp(Cmd_Argv(1),"toggle") == 0) + bgmloop = !bgmloop; + + if (bgmstream) bgmstream->loop = bgmloop; + } + + if (bgmloop) + Con_Printf("Music will be looped\n"); + else + Con_Printf("Music will not be looped\n"); +} + +static void BGM_Stop_f (void) +{ + BGM_Stop(); +} + +static void BGM_Jump_f (void) +{ + if (Cmd_Argc() != 2) { + Con_Printf ("music_jump \n"); + } + else if (bgmstream) { + S_CodecJumpToOrder(bgmstream, atoi(Cmd_Argv(1))); + } +} + +qboolean BGM_Init (void) +{ + music_handler_t *handlers = NULL; + int i; + + Cvar_RegisterVariable(&bgm_extmusic); + Cmd_AddCommand("music", BGM_Play_f); + Cmd_AddCommand("music_pause", BGM_Pause_f); + Cmd_AddCommand("music_resume", BGM_Resume_f); + Cmd_AddCommand("music_loop", BGM_Loop_f); + Cmd_AddCommand("music_stop", BGM_Stop_f); + Cmd_AddCommand("music_jump", BGM_Jump_f); + + if (COM_CheckParm("-noextmusic") != 0) + no_extmusic = true; + + bgmloop = true; + + for (i = 0; wanted_handlers[i].type != CODECTYPE_NONE; i++) + { + switch (wanted_handlers[i].player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + wanted_handlers[i].is_available = + S_CodecIsAvailable(wanted_handlers[i].type); + break; + case BGM_NONE: + default: + break; + } + if (wanted_handlers[i].is_available != -1) + { + if (handlers) + { + handlers->next = &wanted_handlers[i]; + handlers = handlers->next; + } + else + { + music_handlers = &wanted_handlers[i]; + handlers = music_handlers; + } + } + } + + return true; +} + +void BGM_Shutdown (void) +{ + BGM_Stop(); +/* sever our connections to + * midi_drv and snd_codec */ + music_handlers = NULL; +} + +static void BGM_Play_noext (const char *filename, unsigned int allowed_types) +{ + char tmp[MAX_QPATH]; + music_handler_t *handler; + + handler = music_handlers; + while (handler) + { + if (! (handler->type & allowed_types)) + { + handler = handler->next; + continue; + } + if (!handler->is_available) + { + handler = handler->next; + continue; + } + q_snprintf(tmp, sizeof(tmp), "%s/%s.%s", + handler->dir, filename, handler->ext); + switch (handler->player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + bgmstream = S_CodecOpenStreamType(tmp, handler->type, bgmloop); + if (bgmstream) + return; /* success */ + break; + case BGM_NONE: + default: + break; + } + handler = handler->next; + } + + Con_Printf("Couldn't handle music file %s\n", filename); +} + +void BGM_Play (const char *filename) +{ + char tmp[MAX_QPATH]; + const char *ext; + music_handler_t *handler; + + BGM_Stop(); + + if (music_handlers == NULL) + return; + + if (!filename || !*filename) + { + Con_DPrintf("null music file name\n"); + return; + } + + ext = COM_FileGetExtension(filename); + if (! *ext) /* try all things */ + { + BGM_Play_noext(filename, ANY_CODECTYPE); + return; + } + + handler = music_handlers; + while (handler) + { + if (handler->is_available && + !q_strcasecmp(ext, handler->ext)) + break; + handler = handler->next; + } + if (!handler) + { + Con_Printf("Unhandled extension for %s\n", filename); + return; + } + q_snprintf(tmp, sizeof(tmp), "%s/%s", handler->dir, filename); + switch (handler->player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + bgmstream = S_CodecOpenStreamType(tmp, handler->type, bgmloop); + if (bgmstream) + return; /* success */ + break; + case BGM_NONE: + default: + break; + } + + Con_Printf("Couldn't handle music file %s\n", filename); +} + +void BGM_PlayCDtrack (byte track, qboolean looping) +{ +/* instead of searching by the order of music_handlers, do so by + * the order of searchpath priority: the file from the searchpath + * with the highest path_id is most likely from our own gamedir + * itself. This way, if a mod has track02 as a *.mp3 file, which + * is below *.ogg in the music_handler order, the mp3 will still + * have priority over track02.ogg from, say, id1. + */ + char tmp[MAX_QPATH]; + const char *ext; + unsigned int path_id, prev_id, type; + music_handler_t *handler; + + BGM_Stop(); + if (CDAudio_Play(track, looping) == 0) + return; /* success */ + + if (music_handlers == NULL) + return; + + if (no_extmusic || !bgm_extmusic.value) + return; + + prev_id = 0; + type = 0; + ext = NULL; + handler = music_handlers; + while (handler) + { + if (! handler->is_available) + goto _next; + if (! CDRIPTYPE(handler->type)) + goto _next; + q_snprintf(tmp, sizeof(tmp), "%s/track%02d.%s", + MUSIC_DIRNAME, (int)track, handler->ext); + if (! COM_FileExists(tmp, &path_id)) + goto _next; + if (path_id > prev_id) + { + prev_id = path_id; + type = handler->type; + ext = handler->ext; + } + _next: + handler = handler->next; + } + if (ext == NULL) + Con_Printf("Couldn't find a cdrip for track %d\n", (int)track); + else + { + q_snprintf(tmp, sizeof(tmp), "%s/track%02d.%s", + MUSIC_DIRNAME, (int)track, ext); + bgmstream = S_CodecOpenStreamType(tmp, type, bgmloop); + if (! bgmstream) + Con_Printf("Couldn't handle music file %s\n", tmp); + } +} + +void BGM_Stop (void) +{ + if (bgmstream) + { + bgmstream->status = STREAM_NONE; + S_CodecCloseStream(bgmstream); + bgmstream = NULL; + s_rawend = 0; + } +} + +void BGM_Pause (void) +{ + if (bgmstream) + { + if (bgmstream->status == STREAM_PLAY) + bgmstream->status = STREAM_PAUSE; + } +} + +void BGM_Resume (void) +{ + if (bgmstream) + { + if (bgmstream->status == STREAM_PAUSE) + bgmstream->status = STREAM_PLAY; + } +} + +static void BGM_UpdateStream (void) +{ + qboolean did_rewind = false; + int res; /* Number of bytes read. */ + int bufferSamples; + int fileSamples; + int fileBytes; + byte raw[16384]; + + if (bgmstream->status != STREAM_PLAY) + return; + + /* don't bother playing anything if musicvolume is 0 */ + if (bgmvolume.value <= 0) + return; + + /* see how many samples should be copied into the raw buffer */ + if (s_rawend < paintedtime) + s_rawend = paintedtime; + + while (s_rawend < paintedtime + MAX_RAW_SAMPLES) + { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend - paintedtime); + + /* decide how much data needs to be read from the file */ + fileSamples = bufferSamples * bgmstream->info.rate / shm->speed; + if (!fileSamples) + return; + + /* our max buffer size */ + fileBytes = fileSamples * (bgmstream->info.width * bgmstream->info.channels); + if (fileBytes > (int) sizeof(raw)) + { + fileBytes = (int) sizeof(raw); + fileSamples = fileBytes / + (bgmstream->info.width * bgmstream->info.channels); + } + + /* Read */ + res = S_CodecReadStream(bgmstream, fileBytes, raw); + if (res < fileBytes) + { + fileBytes = res; + fileSamples = res / (bgmstream->info.width * bgmstream->info.channels); + } + + if (res > 0) /* data: add to raw buffer */ + { + S_RawSamples(fileSamples, bgmstream->info.rate, + bgmstream->info.width, + bgmstream->info.channels, + raw, bgmvolume.value); + did_rewind = false; + } + else if (res == 0) /* EOF */ + { + if (bgmloop) + { + if (did_rewind) + { + Con_Printf("Stream keeps returning EOF.\n"); + BGM_Stop(); + return; + } + + res = S_CodecRewindStream(bgmstream); + if (res != 0) + { + Con_Printf("Stream seek error (%i), stopping.\n", res); + BGM_Stop(); + return; + } + did_rewind = true; + } + else + { + BGM_Stop(); + return; + } + } + else /* res < 0: some read error */ + { + Con_Printf("Stream read error (%i), stopping.\n", res); + BGM_Stop(); + return; + } + } +} + +void BGM_Update (void) +{ + if (old_volume != bgmvolume.value) + { + if (bgmvolume.value < 0) + Cvar_SetQuick (&bgmvolume, "0"); + else if (bgmvolume.value > 1) + Cvar_SetQuick (&bgmvolume, "1"); + old_volume = bgmvolume.value; + } + if (bgmstream) + BGM_UpdateStream (); +} + diff --git a/Quake/bgmusic.hpp b/Quake/bgmusic.hpp new file mode 100644 index 0000000..ec30820 --- /dev/null +++ b/Quake/bgmusic.hpp @@ -0,0 +1,43 @@ +/* + * Background music handling for Quakespasm (adapted from uHexen2) + * Handles streaming music as raw sound samples and runs the midi driver + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BGMUSIC_H_ +#define _BGMUSIC_H_ + +extern qboolean bgmloop; +extern cvar_t bgm_extmusic; + +qboolean BGM_Init (void); +void BGM_Shutdown (void); + +void BGM_Play (const char *filename); +void BGM_Stop (void); +void BGM_Update (void); +void BGM_Pause (void); +void BGM_Resume (void); + +void BGM_PlayCDtrack (byte track, qboolean looping); + +#endif /* _BGMUSIC_H_ */ + diff --git a/Quake/bspfile.hpp b/Quake/bspfile.hpp new file mode 100644 index 0000000..9803b57 --- /dev/null +++ b/Quake/bspfile.hpp @@ -0,0 +1,421 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __BSPFILE_H +#define __BSPFILE_H + +// upper design bounds + +#define MAX_MAP_HULLS 4 + +#define MAX_MAP_MODELS 256 +#define MAX_MAP_BRUSHES 4096 +#define MAX_MAP_ENTITIES 1024 +#define MAX_MAP_ENTSTRING 65536 + +#define MAX_MAP_PLANES 32767 +#define MAX_MAP_NODES 32767 // because negative shorts are contents +#define MAX_MAP_CLIPNODES 32767 +//#define MAX_MAP_LEAFS 80000 //johnfitz -- was 8192 +#define MAX_MAP_VERTS 65535 +#define MAX_MAP_FACES 65535 +#define MAX_MAP_MARKSURFACES 65535 +#define MAX_MAP_TEXINFO 4096 +#define MAX_MAP_EDGES 256000 +#define MAX_MAP_SURFEDGES 512000 +#define MAX_MAP_TEXTURES 512 +#define MAX_MAP_MIPTEX 0x200000 +#define MAX_MAP_LIGHTING 0x100000 +#define MAX_MAP_VISIBILITY 0x100000 + +#define MAX_MAP_PORTALS 65536 + +// key / value pair sizes + +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +//============================================================================= + + +#define BSPVERSION 29 + +/* RMQ support (2PSB). 32bits instead of shorts for all but bbox sizes (which + * still use shorts) */ +#define BSP2VERSION_2PSB (('B' << 24) | ('S' << 16) | ('P' << 8) | '2') + +/* BSP2 support. 32bits instead of shorts for everything (bboxes use floats) */ +#define BSP2VERSION_BSP2 (('B' << 0) | ('S' << 8) | ('P' << 16) | ('2'<<24)) + +// Quake64 +#define BSPVERSION_QUAKE64 (('Q' << 24) | ('6' << 16) | ('4' << 8) | ' ') + +#define TOOLVERSION 2 + +typedef struct +{ + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 + +#define HEADER_LUMPS 15 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} dmodel_t; + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + +// Quake64 +typedef struct miptex64_s +{ + char name[16]; + unsigned width, height; + unsigned shift; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} miptex64_t; + +typedef struct +{ + float point[3]; +} dvertex_t; + + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 + +typedef struct +{ + float normal[3]; + float dist; + int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} dplane_t; + + +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 + + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} dsnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned int firstface; + unsigned int numfaces; // counting both sides +} dl1node_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + float mins[3]; // for sphere culling + float maxs[3]; + unsigned int firstface; + unsigned int numfaces; // counting both sides +} dl2node_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dsclipnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are contents +} dlclipnode_t; + + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} texinfo_t; +#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision +#define TEX_MISSING 2 // johnfitz -- this texinfo does not have a texture + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} dsedge_t; + +typedef struct +{ + unsigned int v[2]; // vertex numbers +} dledge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dsface_t; + +typedef struct +{ + int planenum; + int side; + + int firstedge; // we must support > 64k edges + int numedges; + int texinfo; + +// lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dlface_t; + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dsleaf_t; + +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned int firstmarksurface; + unsigned int nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dl1leaf_t; + +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + float mins[3]; // for frustum culling + float maxs[3]; + + unsigned int firstmarksurface; + unsigned int nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dl2leaf_t; + + +//============================================================================ + +#ifndef QUAKE_GAME + +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + + +// the utilities get to be lazy and just use large static arrays + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; + +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; + +extern int texdatasize; +extern byte dtexdata[MAX_MAP_MIPTEX]; // (dmiptexlump_t) + +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; + +//extern int numleafs; +//extern dleaf_t dleafs[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern int numtexinfo; +extern texinfo_t texinfo[MAX_MAP_TEXINFO]; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; + +extern int numclipnodes; +extern dclipnode_t dclipnodes[MAX_MAP_CLIPNODES]; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; + +extern int nummarksurfaces; +extern unsigned short dmarksurfaces[MAX_MAP_MARKSURFACES]; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; + + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void LoadBSPFile (char *filename); +void WriteBSPFile (char *filename); +void PrintBSPFileSizes (void); + +//=============== + + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; +} entity_t; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +void ParseEntities (void); +void UnparseEntities (void); + +void SetKeyValue (entity_t *ent, char *key, char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present + +vec_t FloatForKey (entity_t *ent, char *key); +void GetVectorForKey (entity_t *ent, char *key, vec3_t vec); + +epair_t *ParseEpair (void); + +#endif /* QUAKE_GAME */ + +#endif /* __BSPFILE_H */ + diff --git a/Quake/cd_null.cpp b/Quake/cd_null.cpp new file mode 100644 index 0000000..87afc7b --- /dev/null +++ b/Quake/cd_null.cpp @@ -0,0 +1,53 @@ +/* + * cd_null.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "quakedef.h" + +int CDAudio_Play(byte track, qboolean looping) +{ + return -1; +} + +void CDAudio_Stop(void) +{ +} + +void CDAudio_Pause(void) +{ +} + +void CDAudio_Resume(void) +{ +} + +void CDAudio_Update(void) +{ +} + +int CDAudio_Init(void) +{ + Con_Printf("CDAudio disabled at compile time\n"); + return -1; +} + +void CDAudio_Shutdown(void) +{ +} + diff --git a/Quake/cd_sdl.cpp b/Quake/cd_sdl.cpp new file mode 100644 index 0000000..1acd9d9 --- /dev/null +++ b/Quake/cd_sdl.cpp @@ -0,0 +1,593 @@ +/* + * cd_sdl.c + * + * Copyright (C) 1996-1997 Id Software, Inc. + * Taken from the Twilight project with modifications + * to make it work with Hexen II: Hammer of Thyrion. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + +#ifndef SDL_INIT_CDROM + +/* SDL dropped support for + cd audio since v1.3.0 */ +#pragma message("Warning: SDL CDAudio support disabled") +#include "cd_null.c" + +#else /* SDL_INIT_CDROM */ + +#include "quakedef.hpp" + +static qboolean cdValid = false; +static qboolean playing = false; +static qboolean wasPlaying = false; +static qboolean enabled = true; +static qboolean playLooping = false; +static byte remap[100]; +static byte playTrack; +static double endOfTrack = -1.0, pausetime = -1.0; +static SDL_CD *cd_handle; +static int cd_dev = -1; +static float old_cdvolume; +static qboolean hw_vol_works = true; + + +static void CDAudio_Eject(void) +{ + if (!cd_handle || !enabled) + return; + +#ifdef __linux__ + SDL_CDStop(cd_handle); /* see CDAudio_Stop() */ +#endif + if (SDL_CDEject(cd_handle) < 0) + Con_Printf ("Unable to eject CD-ROM: %s\n", SDL_GetError ()); +} + +static int CDAudio_GetAudioDiskInfo(void) +{ + cdValid = false; + + if (!cd_handle) + return -1; + + if ( ! CD_INDRIVE(SDL_CDStatus(cd_handle)) ) + return -1; + + cdValid = true; + + return 0; +} + +int CDAudio_Play(byte track, qboolean looping) +{ + int len_m, len_s, len_f; + + if (!cd_handle || !enabled) + return -1; + + if (!cdValid) + { + CDAudio_GetAudioDiskInfo(); + if (!cdValid) + return -1; + } + + track = remap[track]; + + if (track < 1 || track > cd_handle->numtracks) + { + Con_Printf ("CDAudio_Play: Bad track number %d.\n", track); + return -1; + } + + if (cd_handle->track[track-1].type == SDL_DATA_TRACK) + { + Con_Printf ("CDAudio_Play: track %d is not audio\n", track); + return -1; + } + + if (playing) + { + if (playTrack == track) + return 0; + CDAudio_Stop(); + } + + if (SDL_CDPlay(cd_handle, cd_handle->track[track-1].offset, cd_handle->track[track-1].length) < 0) + { + Con_Printf ("CDAudio_Play: Unable to play track %d: %s\n", track, SDL_GetError ()); + return -1; + } + + playLooping = looping; + playTrack = track; + playing = true; + + FRAMES_TO_MSF(cd_handle->track[track-1].length, &len_m, &len_s, &len_f); + endOfTrack = realtime + ((double)len_m * 60.0) + (double)len_s + (double)len_f / (double)CD_FPS; + + /* Add the pregap for the next track. This means that disc-at-once CDs + * won't loop smoothly, but they wouldn't anyway so it doesn't really + * matter. SDL doesn't give us pregap information anyway, so you'll + * just have to live with it. */ + endOfTrack += 2.0; + pausetime = -1.0; + + if (bgmvolume.value == 0) /* don't bother advancing */ + CDAudio_Pause (); + + return 0; +} + +void CDAudio_Stop(void) +{ + if (!cd_handle || !enabled) + return; + + if (!playing) + return; + +#ifdef __linux__ + /* Don't really stop, but just pause: On some devices, the CDROMSTOP + * ioctl causes any followup ioctls to fail for a considerable time. + * observed with a TSSTcorp CDW/DVD SH-M522C drive with TS05 and TS08 + * firmware versions running under a 2.6.27.25 kernel, and with a + * Samsung DVD r/w drive running under 2.6.35.6 kernel. + * Therefore, avoid dead stops if playback may be resumed shortly. */ + if (SDL_CDPause(cd_handle) < 0) + Con_Printf ("CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError()); +#else + if (SDL_CDStop(cd_handle) < 0) + Con_Printf ("CDAudio_Stop: Unable to stop CD-ROM (%s)\n", SDL_GetError()); +#endif + + wasPlaying = false; + playing = false; + pausetime = -1.0; + endOfTrack = -1.0; +} + +void CDAudio_Pause(void) +{ + if (!cd_handle || !enabled) + return; + + if (!playing) + return; + + if (SDL_CDPause(cd_handle) < 0) + Con_Printf ("Unable to pause CD-ROM: %s\n", SDL_GetError()); + + wasPlaying = playing; + playing = false; + pausetime = realtime; +} + +void CDAudio_Resume(void) +{ + if (!cd_handle || !enabled) + return; + + if (!cdValid) + return; + + if (!wasPlaying) + return; + + if (SDL_CDResume(cd_handle) < 0) + Con_Printf ("Unable to resume CD-ROM: %s\n", SDL_GetError()); + playing = true; + endOfTrack += realtime - pausetime; + pausetime = -1.0; +} + +static int get_first_audiotrk (void) +{ + int i; + for (i = 0; i < cd_handle->numtracks; ++i) + if (cd_handle->track[i].type != SDL_DATA_TRACK) + return ++i; + return 1; +} + +static void CD_f (void) +{ + const char *command; + int ret, n; + + if (Cmd_Argc() < 2) + { + Con_Printf("commands:"); + Con_Printf("on, off, reset, remap, \n"); + Con_Printf("play, stop, loop, pause, resume\n"); + Con_Printf("eject, info, next, prev\n"); + return; + } + + command = Cmd_Argv (1); + + if (q_strcasecmp(command, "on") == 0) + { + enabled = true; + return; + } + + if (q_strcasecmp(command, "off") == 0) + { + if (playing) + CDAudio_Stop(); + enabled = false; + return; + } + + if (q_strcasecmp(command, "reset") == 0) + { + enabled = true; + if (playing) + CDAudio_Stop(); + for (n = 0; n < 100; n++) + remap[n] = n; + CDAudio_GetAudioDiskInfo(); + return; + } + + if (q_strcasecmp(command, "remap") == 0) + { + ret = Cmd_Argc() - 2; + if (ret <= 0) + { + for (n = 1; n < 100; n++) + if (remap[n] != n) + Con_Printf(" %u -> %u\n", n, remap[n]); + return; + } + for (n = 1; n <= ret; n++) + remap[n] = atoi(Cmd_Argv (n + 1)); + return; + } + + if (!cdValid) + { + CDAudio_GetAudioDiskInfo(); + if (!cdValid) + { + Con_Printf("No CD in player.\n"); + return; + } + } + + if (q_strcasecmp(command, "play") == 0) + { + n = atoi(Cmd_Argv (2)); + if (n == 0) + n = 1; + CDAudio_Play((byte)n, false); + return; + } + + if (q_strcasecmp(command, "loop") == 0) + { + CDAudio_Play((byte)atoi(Cmd_Argv (2)), true); + return; + } + + if (q_strcasecmp(command, "stop") == 0) + { + CDAudio_Stop(); + return; + } + + if (q_strcasecmp(command, "pause") == 0) + { + CDAudio_Pause(); + return; + } + + if (q_strcasecmp(command, "resume") == 0) + { + CDAudio_Resume(); + return; + } + + if (q_strcasecmp(command, "eject") == 0) + { + if (playing) + CDAudio_Stop(); + CDAudio_Eject(); + cdValid = false; + return; + } + + if (q_strcasecmp(command, "info") == 0) + { + int current_min, current_sec, current_frame; + int length_min, length_sec, length_frame; + + Con_Printf ("%u tracks\n", cd_handle->numtracks); + + if (playing) + Con_Printf("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack); + else if (wasPlaying) + Con_Printf("Paused %s track %u\n", playLooping ? "looping" : "playing", playTrack); + + if (playing || wasPlaying) + { + SDL_CDStatus(cd_handle); + FRAMES_TO_MSF(cd_handle->cur_frame, ¤t_min, ¤t_sec, ¤t_frame); + FRAMES_TO_MSF(cd_handle->track[playTrack-1].length, &length_min, &length_sec, &length_frame); + + Con_Printf ("Current position: %d:%02d.%02d (of %d:%02d.%02d)\n", + current_min, current_sec, current_frame * 60 / CD_FPS, + length_min, length_sec, length_frame * 60 / CD_FPS); + } + Con_Printf("Volume is %f\n", bgmvolume.value); + + return; + } + + if (q_strcasecmp(command, "next") == 0) + { + if (playTrack == cd_handle->numtracks) + playTrack = get_first_audiotrk() - 1; + CDAudio_Play(playTrack + 1, playLooping); + return; + } + + if (q_strcasecmp(command, "prev") == 0) + { + if (playTrack == get_first_audiotrk()) + playTrack = cd_handle->numtracks + 1; + CDAudio_Play(playTrack - 1, playLooping); + return; + } + + Con_Printf ("cd: unknown command \"%s\"\n", command); +} + +static qboolean CD_GetVolume (void *unused) +{ +/* FIXME: write proper code in here when SDL + supports cdrom volume control some day. */ + return false; +} + +static qboolean CD_SetVolume (void *unused) +{ +/* FIXME: write proper code in here when SDL + supports cdrom volume control some day. */ + return false; +} + +static qboolean CDAudio_SetVolume (float value) +{ + if (!cd_handle || !enabled) + return false; + + old_cdvolume = value; + + if (value == 0.0f) + CDAudio_Pause (); + else + CDAudio_Resume(); + + if (!hw_vol_works) + { + return false; + } + else + { +/* FIXME: write proper code in here when SDL + supports cdrom volume control some day. */ + return CD_SetVolume (NULL); + } +} + +void CDAudio_Update(void) +{ + CDstatus curstat; +/* static double lastchk;*/ + + if (!cd_handle || !enabled) + return; + + if (old_cdvolume != bgmvolume.value) + CDAudio_SetVolume (bgmvolume.value); + +/* if (playing && realtime > lastchk)*/ + if (playing && realtime > endOfTrack) + { + /* lastchk = realtime + 2;*/ /* two seconds between chks */ + curstat = SDL_CDStatus(cd_handle); + if (curstat != CD_PLAYING && curstat != CD_PAUSED) + { + playing = false; + endOfTrack = -1.0; + if (playLooping) + CDAudio_Play(playTrack, true); + } + } +} + +static const char *get_cddev_arg (const char *arg) +{ +#if defined(_WIN32) +/* arg should be like "D:\", make sure it is so, + * but tolerate args like "D" or "D:", as well. */ + static char drive[4]; + if (!arg || ! *arg) + return NULL; + if (arg[1] != '\0') + { + if (arg[1] != ':') + return NULL; + if (arg[2] != '\0') + { + if (arg[2] != '\\' && + arg[2] != '/') + return NULL; + if (arg[3] != '\0') + return NULL; + } + } + if (*arg >= 'A' && *arg <= 'Z') + { + drive[0] = *arg; + drive[1] = ':'; + drive[2] = '\\'; + drive[3] = '\0'; + return drive; + } + else if (*arg >= 'a' && *arg <= 'z') + { + /* make it uppercase for SDL */ + drive[0] = *arg - ('a' - 'A'); + drive[1] = ':'; + drive[2] = '\\'; + drive[3] = '\0'; + return drive; + } + return NULL; +#else + if (!arg || ! *arg) + return NULL; + return arg; +#endif +} + +static void export_cddev_arg (void) +{ +/* Bad ugly hack to workaround SDL's cdrom device detection. + * not needed for windows due to the way SDL_cdrom works. */ +#if !defined(_WIN32) + int i = COM_CheckParm("-cddev"); + if (i != 0 && i < com_argc - 1 && com_argv[i+1][0] != '\0') + { + static char arg[64]; + q_snprintf(arg, sizeof(arg), "SDL_CDROM=%s", com_argv[i+1]); + putenv(arg); + } +#endif +} + +int CDAudio_Init(void) +{ + int i, sdl_num_drives; + + if (safemode || COM_CheckParm("-nocdaudio")) + return -1; + + export_cddev_arg(); + + if (SDL_InitSubSystem(SDL_INIT_CDROM) < 0) + { + Con_Printf("Couldn't init SDL cdrom: %s\n", SDL_GetError()); + return -1; + } + + sdl_num_drives = SDL_CDNumDrives (); + Con_Printf ("SDL detected %d CD-ROM drive%c\n", sdl_num_drives, + sdl_num_drives == 1 ? ' ' : 's'); + + if (sdl_num_drives < 1) + return -1; + + if ((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1) + { + const char *userdev = get_cddev_arg(com_argv[i+1]); + if (!userdev) + { + Con_Printf("Invalid argument to -cddev\n"); + return -1; + } + for (i = 0; i < sdl_num_drives; i++) + { + if (!q_strcasecmp(SDL_CDName(i), userdev)) + { + cd_dev = i; + break; + } + } + if (cd_dev == -1) + { + Con_Printf("SDL couldn't find cdrom device %s\n", userdev); + return -1; + } + } + + if (cd_dev == -1) + cd_dev = 0; /* default drive */ + + cd_handle = SDL_CDOpen(cd_dev); + if (!cd_handle) + { + Con_Printf ("CDAudio_Init: Unable to open CD-ROM drive %s (%s)\n", + SDL_CDName(cd_dev), SDL_GetError()); + return -1; + } + + for (i = 0; i < 100; i++) + remap[i] = i; + enabled = true; + old_cdvolume = bgmvolume.value; + + Con_Printf("CDAudio initialized (SDL, using %s)\n", SDL_CDName(cd_dev)); + + if (CDAudio_GetAudioDiskInfo()) + { + Con_Printf("CDAudio_Init: No CD in drive\n"); + cdValid = false; + } + + Cmd_AddCommand ("cd", CD_f); + + hw_vol_works = CD_GetVolume (NULL); /* no SDL support at present. */ + if (hw_vol_works) + hw_vol_works = CDAudio_SetVolume (bgmvolume.value); + + return 0; +} + +void CDAudio_Shutdown(void) +{ + if (!cd_handle) + return; + CDAudio_Stop(); + if (hw_vol_works) + CD_SetVolume (NULL); /* no SDL support at present. */ +#ifdef __linux__ + SDL_CDStop(cd_handle); /* see CDAudio_Stop() */ +#endif + SDL_CDClose(cd_handle); + cd_handle = NULL; + cd_dev = -1; + SDL_QuitSubSystem(SDL_INIT_CDROM); +} + +#endif /* SDL_INIT_CDROM */ + diff --git a/Quake/cdaudio.hpp b/Quake/cdaudio.hpp new file mode 100644 index 0000000..7c8477f --- /dev/null +++ b/Quake/cdaudio.hpp @@ -0,0 +1,35 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __CDAUDIO_H +#define __CDAUDIO_H + +int CDAudio_Init (void); +int CDAudio_Play (byte track, qboolean looping); + /* returns 0 for success, -1 for failure. */ +void CDAudio_Stop (void); +void CDAudio_Pause (void); +void CDAudio_Resume (void); +void CDAudio_Shutdown (void); +void CDAudio_Update (void); + +#endif /* __CDAUDIO_H */ + diff --git a/Quake/cfgfile.cpp b/Quake/cfgfile.cpp new file mode 100644 index 0000000..00c459d --- /dev/null +++ b/Quake/cfgfile.cpp @@ -0,0 +1,175 @@ +/* + * cfgfile.c -- misc reads from the config file + * + * Copyright (C) 2008-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + + +static fshandle_t *cfg_file; + +/* +=================== +CFG_ReadCvars + +used for doing early reads from config.cfg searching the list +of given cvar names for the user-set values. a temporary +solution until we merge a better cvar system. +the num_vars argument must be the exact number of strings in the +array, otherwise I have nothing against going out of bounds. +=================== +*/ +void CFG_ReadCvars (const char **vars, int num_vars) +{ + char buff[1024], *tmp; + int i, j; + + if (!cfg_file || num_vars < 1) + return; + + j = 0; + + do { + i = 0; + memset (buff, 0, sizeof(buff)); + // we expect a line in the format that Cvar_WriteVariables + // writes to the config file. although I'm trying to be as + // much cautious as possible, if the user screws it up by + // editing it, it's his fault. + if (FS_fgets(buff, sizeof(buff), cfg_file)) + { + // remove end-of-line characters + while (buff[i]) + { + if (buff[i] == '\r' || buff[i] == '\n') + buff[i] = '\0'; + // while we're here, replace tabs with spaces + if (buff[i] == '\t') + buff[i] = ' '; + i++; + } + // go to the last character + while (buff[i] == 0 && i > 0) + i--; + // remove trailing spaces + while (i > 0) + { + if (buff[i] == ' ') + { + buff[i] = '\0'; + i--; + } + else + break; + } + + // the line must end with a quotation mark + if (buff[i] != '\"') + continue; + buff[i] = '\0'; + + for (i = 0; i < num_vars && vars[i]; i++) + { + // look for the cvar name + one space + tmp = strstr(buff, va("%s ",vars[i])); + if (tmp != buff) + continue; + // locate the first quotation mark + tmp = strchr(buff, '\"'); + if (tmp) + { + Cvar_Set (vars[i], tmp + 1); + j++; + break; + } + } + } + + if (j == num_vars) + break; + + } while (!FS_feof(cfg_file) && !FS_ferror(cfg_file)); + + FS_rewind (cfg_file); +} + +/* +=================== +CFG_ReadCvarOverrides + +convenience function, reading the "+" command line override +values of cvars in the given list. doesn't do anything with +the config file. +=================== +*/ +void CFG_ReadCvarOverrides (const char **vars, int num_vars) +{ + char buff[64]; + int i, j; + + if (num_vars < 1) + return; + + buff[0] = '+'; + + for (i = 0; i < num_vars; i++) + { + q_strlcpy (&buff[1], vars[i], sizeof(buff) - 1); + j = COM_CheckParm(buff); + if (j != 0 && j < com_argc - 1) + { + if (com_argv[j + 1][0] != '-' && com_argv[j + 1][0] != '+') + Cvar_Set(vars[i], com_argv[j + 1]); + } + } +} + +void CFG_CloseConfig (void) +{ + if (cfg_file) + { + FS_fclose(cfg_file); + Z_Free(cfg_file); + cfg_file = NULL; + } +} + +int CFG_OpenConfig (const char *cfg_name) +{ + FILE *f; + long length; + qboolean pak; + + CFG_CloseConfig (); + + length = (long) COM_FOpenFile (cfg_name, &f, NULL); + pak = file_from_pak; + if (length == -1) + return -1; + + cfg_file = (fshandle_t *) Z_Malloc(sizeof(fshandle_t)); + cfg_file->file = f; + cfg_file->start = ftell(f); + cfg_file->pos = 0; + cfg_file->length = length; + cfg_file->pak = pak; + + return 0; +} + diff --git a/Quake/cfgfile.hpp b/Quake/cfgfile.hpp new file mode 100644 index 0000000..dbe8f39 --- /dev/null +++ b/Quake/cfgfile.hpp @@ -0,0 +1,42 @@ +/* + * cfgfile.h -- misc reads from the config file + * + * Copyright (C) 2008-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __CFGFILE_H +#define __CFGFILE_H + +int CFG_OpenConfig (const char *cfg_name); +// opens the given config file. only one open config file is +// kept: previosly opened one, if any, will be closed. + +void CFG_CloseConfig (void); +// closes the currently open config file. + +void CFG_ReadCvars (const char **vars, int num_vars); +// reads the values of cvars in the given list from the opened +// config file. + +void CFG_ReadCvarOverrides (const char **vars, int num_vars); +// convenience function, reading the "+" command line override +// values of cvars in the given list. doesn't do anything with +// the config file. call this after CFG_ReadCvars() and before +// locking your cvars. + +#endif /* __CFGFILE_H */ diff --git a/Quake/chase.cpp b/Quake/chase.cpp new file mode 100644 index 0000000..870312b --- /dev/null +++ b/Quake/chase.cpp @@ -0,0 +1,118 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// chase.c -- chase camera code + +#include "quakedef.hpp" + +cvar_t chase_back = {"chase_back", "100", CVAR_NONE}; +cvar_t chase_up = {"chase_up", "16", CVAR_NONE}; +cvar_t chase_right = {"chase_right", "0", CVAR_NONE}; +cvar_t chase_active = {"chase_active", "0", CVAR_NONE}; + +/* +============== +Chase_Init +============== +*/ +void Chase_Init (void) +{ + Cvar_RegisterVariable (&chase_back); + Cvar_RegisterVariable (&chase_up); + Cvar_RegisterVariable (&chase_right); + Cvar_RegisterVariable (&chase_active); +} + +/* +============== +TraceLine + +TODO: impact on bmodels, monsters +============== +*/ +void TraceLine (vec3_t start, vec3_t end, vec3_t impact) +{ + trace_t trace; + + memset (&trace, 0, sizeof(trace)); + SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace); + + VectorCopy (trace.endpos, impact); +} + +/* +============== +Chase_UpdateForClient -- johnfitz -- orient client based on camera. called after input +============== +*/ +void Chase_UpdateForClient (void) +{ + //place camera + + //assign client angles to camera + + //see where camera points + + //adjust client angles to point at the same place +} + +/* +============== +Chase_UpdateForDrawing -- johnfitz -- orient camera based on client. called before drawing + +TODO: stay at least 8 units away from all walls in this leaf +============== +*/ +void Chase_UpdateForDrawing (void) +{ + int i; + vec3_t forward, up, right; + vec3_t ideal, crosshair, temp; + + AngleVectors (cl.viewangles, forward, right, up); + + // calc ideal camera location before checking for walls + for (i=0 ; i<3 ; i++) + ideal[i] = cl.viewent.origin[i] + - forward[i]*chase_back.value + + right[i]*chase_right.value; + //+ up[i]*chase_up.value; + ideal[2] = cl.viewent.origin[2] + chase_up.value; + + // make sure camera is not in or behind a wall + TraceLine(r_refdef.vieworg, ideal, temp); + if (VectorLength(temp) != 0) + VectorCopy(temp, ideal); + + // place camera + VectorCopy (ideal, r_refdef.vieworg); + + // find the spot the player is looking at + VectorMA (cl.viewent.origin, 1<<20, forward, temp); + TraceLine (cl.viewent.origin, temp, crosshair); + + // calculate camera angles to look at the same spot + VectorSubtract (crosshair, r_refdef.vieworg, temp); + VectorAngles (temp, r_refdef.viewangles); + if (r_refdef.viewangles[PITCH] == 90 || r_refdef.viewangles[PITCH] == -90) + r_refdef.viewangles[YAW] = cl.viewangles[YAW]; +} + diff --git a/Quake/cl_demo.cpp b/Quake/cl_demo.cpp new file mode 100644 index 0000000..331f975 --- /dev/null +++ b/Quake/cl_demo.cpp @@ -0,0 +1,503 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include + +#include "quakedef.hpp" + +static void CL_FinishTimeDemo (void); + +/* +============================================================================== + +DEMO CODE + +When a demo is playing back, all NET_SendMessages are skipped, and +NET_GetMessages are read from the demo file. + +Whenever cl.time gets past the last received message, another message is +read from the demo file. +============================================================================== +*/ + +// from ProQuake: space to fill out the demo header for record at any time +static byte *demo_head; +static int *demo_head_sizes; + +/* +============== +CL_ClearSignons +============== +*/ +void CL_ClearSignons (void) +{ + VEC_CLEAR (demo_head); + VEC_CLEAR (demo_head_sizes); + cls.signon = 0; +} + +/* +============== +CL_StopPlayback + +Called when a demo file runs out, or the user starts a game +============== +*/ +void CL_StopPlayback (void) +{ + if (!cls.demoplayback) + return; + + fclose (cls.demofile); + cls.demoplayback = false; + cls.demopaused = false; + cls.demofile = NULL; + cls.state = ca_disconnected; + + if (cls.timedemo) + CL_FinishTimeDemo (); +} + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length and view angles +==================== +*/ +static void CL_WriteDemoMessage (void) +{ + int len; + int i; + float f; + + len = LittleLong (net_message.cursize); + fwrite (&len, 4, 1, cls.demofile); + for (i = 0; i < 3; i++) + { + f = LittleFloat (cl.viewangles[i]); + fwrite (&f, 4, 1, cls.demofile); + } + fwrite (net_message.data, net_message.cursize, 1, cls.demofile); + fflush (cls.demofile); +} + +static int CL_GetDemoMessage (void) +{ + int r, i; + float f; + + if (cls.demopaused) + return 0; + + // decide if it is time to grab the next message + if (cls.signon == SIGNONS) // always grab until fully connected + { + if (cls.timedemo) + { + if (host_framecount == cls.td_lastframe) + return 0; // already read this frame's message + cls.td_lastframe = host_framecount; + // if this is the second frame, grab the real td_starttime + // so the bogus time on the first frame doesn't count + if (host_framecount == cls.td_startframe + 1) + cls.td_starttime = realtime; + } + else if (/* cl.time > 0 && */ cl.time <= cl.mtime[0]) + { + return 0; // don't need another message yet + } + } + +// get the next message + if (! fread(&net_message.cursize, 4, 1, cls.demofile)) + Sys_Error ("Demo read error"); + VectorCopy (cl.mviewangles[0], cl.mviewangles[1]); + for (i = 0 ; i < 3 ; i++) + { + r = fread (&f, 4, 1, cls.demofile); + cl.mviewangles[0][i] = LittleFloat (f); + } + + net_message.cursize = LittleLong (net_message.cursize); + if (net_message.cursize > MAX_MSGLEN) + Sys_Error ("Demo message > MAX_MSGLEN"); + r = fread (net_message.data, net_message.cursize, 1, cls.demofile); + if (r != 1) + { + CL_StopPlayback (); + return 0; + } + + return 1; +} + +/* +==================== +CL_GetMessage + +Handles recording and playback of demos, on top of NET_ code +==================== +*/ +int CL_GetMessage (void) +{ + int r; + + if (cls.demoplayback) + return CL_GetDemoMessage (); + + while (1) + { + r = NET_GetMessage (cls.netcon); + + if (r != 1 && r != 2) + return r; + + // discard nop keepalive message + if (net_message.cursize == 1 && net_message.data[0] == svc_nop) + Con_Printf ("<-- server to client keepalive\n"); + else + break; + } + + if (cls.demorecording) + CL_WriteDemoMessage (); + + if (cls.signon < 2) + { + // record messages before full connection, so that a + // demo record can happen after connection is done + Vec_Append ((void**)&demo_head, 1, net_message.data, net_message.cursize); + VEC_PUSH (demo_head_sizes, net_message.cursize); + } + + return r; +} + + +/* +==================== +CL_Stop_f + +stop recording a demo +==================== +*/ +void CL_Stop_f (void) +{ + if (cmd_source != src_command) + return; + + if (!cls.demorecording) + { + Con_Printf ("Not recording a demo.\n"); + return; + } + +// write a disconnect message to the demo file + SZ_Clear (&net_message); + MSG_WriteByte (&net_message, svc_disconnect); + CL_WriteDemoMessage (); + +// finish up + fclose (cls.demofile); + cls.demofile = NULL; + cls.demorecording = false; + Con_Printf ("Completed demo\n"); + +// ericw -- update demo tab-completion list + DemoList_Rebuild (); +} + +/* +==================== +CL_Record_f + +record [cd track] +==================== +*/ +void CL_Record_f (void) +{ + int c; + char name[MAX_OSPATH]; + int track; + + if (cmd_source != src_command) + return; + + if (cls.demoplayback) + { + Con_Printf ("Can't record during demo playback\n"); + return; + } + + if (cls.demorecording) + CL_Stop_f(); + + c = Cmd_Argc(); + if (c != 2 && c != 3 && c != 4) + { + Con_Printf ("record [ [cd track]]\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Printf ("Relative pathnames are not allowed.\n"); + return; + } + + if (c == 2 && cls.state == ca_connected) + { +#if 0 + Con_Printf("Can not record - already connected to server\nClient demo recording must be started before connecting\n"); + return; +#endif + if (cls.signon < 2) + { + Con_Printf("Can't record - try again when connected\n"); + return; + } + } + +// write the forced cd track number, or -1 + if (c == 4) + { + track = atoi(Cmd_Argv(3)); + Con_Printf ("Forcing CD track to %i\n", cls.forcetrack); + } + else + { + track = -1; + } + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1)); + +// start the map up + if (c > 2) + { + Cmd_ExecuteString ( va("map %s", Cmd_Argv(2)), src_command); + if (cls.state != ca_connected) + return; + } + +// open the demo file + COM_AddExtension (name, ".dem", sizeof(name)); + + Con_Printf ("recording to %s.\n", name); + cls.demofile = fopen (name, "wb"); + if (!cls.demofile) + { + Con_Printf ("ERROR: couldn't create %s\n", name); + return; + } + + cls.forcetrack = track; + fprintf (cls.demofile, "%i\n", cls.forcetrack); + + cls.demorecording = true; + + // from ProQuake: initialize the demo file if we're already connected + if (c == 2 && cls.state == ca_connected) + { + static byte tmpbuf[NET_MAXMESSAGE]; + byte *data = net_message.data; + int cursize = net_message.cursize; + int maxsize = net_message.maxsize; + int i, count; + + net_message.data = demo_head; + for (i = 0, count = VEC_SIZE (demo_head_sizes); i < count; i++) + { + net_message.cursize = demo_head_sizes[i]; + CL_WriteDemoMessage (); + net_message.data += net_message.cursize; + } + + net_message.data = tmpbuf; + net_message.maxsize = sizeof (tmpbuf); + SZ_Clear (&net_message); + + // current names, colors, and frag counts + for (i = 0; i < cl.maxclients; i++) + { + MSG_WriteByte (&net_message, svc_updatename); + MSG_WriteByte (&net_message, i); + MSG_WriteString (&net_message, cl.scores[i].name); + MSG_WriteByte (&net_message, svc_updatefrags); + MSG_WriteByte (&net_message, i); + MSG_WriteShort (&net_message, cl.scores[i].frags); + MSG_WriteByte (&net_message, svc_updatecolors); + MSG_WriteByte (&net_message, i); + MSG_WriteByte (&net_message, cl.scores[i].colors); + } + + // send all current light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + MSG_WriteByte (&net_message, svc_lightstyle); + MSG_WriteByte (&net_message, i); + MSG_WriteString (&net_message, cl_lightstyle[i].map); + } + + // what about the CD track or SVC fog... future consideration. + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_TOTALSECRETS); + MSG_WriteLong (&net_message, cl.stats[STAT_TOTALSECRETS]); + + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_TOTALMONSTERS); + MSG_WriteLong (&net_message, cl.stats[STAT_TOTALMONSTERS]); + + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_SECRETS); + MSG_WriteLong (&net_message, cl.stats[STAT_SECRETS]); + + MSG_WriteByte (&net_message, svc_updatestat); + MSG_WriteByte (&net_message, STAT_MONSTERS); + MSG_WriteLong (&net_message, cl.stats[STAT_MONSTERS]); + + // view entity + MSG_WriteByte (&net_message, svc_setview); + MSG_WriteShort (&net_message, cl.viewentity); + + // signon + MSG_WriteByte (&net_message, svc_signonnum); + MSG_WriteByte (&net_message, 3); + + CL_WriteDemoMessage(); + + // restore net_message + net_message.data = data; + net_message.cursize = cursize; + net_message.maxsize = maxsize; + } +} + + +/* +==================== +CL_PlayDemo_f + +play [demoname] +==================== +*/ +void CL_PlayDemo_f (void) +{ + char name[MAX_OSPATH]; + + if (cmd_source != src_command) + return; + + if (Cmd_Argc() != 2) + { + Con_Printf ("playdemo : plays a demo\n"); + return; + } + +// disconnect from server + CL_Disconnect (); + +// open the demo file + std::strncpy(name, Cmd_Argv(1), sizeof(name)); + COM_AddExtension (name, ".dem", sizeof(name)); + + Con_Printf ("Playing demo from %s.\n", name); + + COM_FOpenFile (name, &cls.demofile, NULL); + if (!cls.demofile) + { + Con_Printf ("ERROR: couldn't open %s\n", name); + cls.demonum = -1; // stop demo loop + return; + } + +// ZOID, fscanf is evil +// O.S.: if a space character e.g. 0x20 (' ') follows '\n', +// fscanf skips that byte too and screws up further reads. +// fscanf (cls.demofile, "%i\n", &cls.forcetrack); + if (fscanf (cls.demofile, "%i", &cls.forcetrack) != 1 || fgetc (cls.demofile) != '\n') + { + fclose (cls.demofile); + cls.demofile = NULL; + cls.demonum = -1; // stop demo loop + Con_Printf ("ERROR: demo \"%s\" is invalid\n", name); + return; + } + + cls.demoplayback = true; + cls.demopaused = false; + cls.state = ca_connected; + +// get rid of the menu and/or console + key_dest = key_game; +} + +/* +==================== +CL_FinishTimeDemo + +==================== +*/ +static void CL_FinishTimeDemo (void) +{ + int frames; + float time; + + cls.timedemo = false; + +// the first frame didn't count + frames = (host_framecount - cls.td_startframe) - 1; + time = realtime - cls.td_starttime; + if (!time) + time = 1; + Con_Printf ("%i frames %5.1f seconds %5.1f fps\n", frames, time, frames/time); +} + +/* +==================== +CL_TimeDemo_f + +timedemo [demoname] +==================== +*/ +void CL_TimeDemo_f (void) +{ + if (cmd_source != src_command) + return; + + if (Cmd_Argc() != 2) + { + Con_Printf ("timedemo : gets demo speeds\n"); + return; + } + + CL_PlayDemo_f (); + if (!cls.demofile) + return; + +// cls.td_starttime will be grabbed at the second frame of the demo, so +// all the loading time doesn't get counted + + cls.timedemo = true; + cls.td_startframe = host_framecount; + cls.td_lastframe = -1; // get a new message this frame +} + diff --git a/Quake/cl_input.cpp b/Quake/cl_input.cpp new file mode 100644 index 0000000..cd72ab7 --- /dev/null +++ b/Quake/cl_input.cpp @@ -0,0 +1,454 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl.input.c -- builds an intended movement command to send to the server + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. + +#include "quakedef.hpp" + +extern cvar_t cl_maxpitch; //johnfitz -- variable pitch clamping +extern cvar_t cl_minpitch; //johnfitz -- variable pitch clamping + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook, in_klook; +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed, in_use, in_jump, in_attack; +kbutton_t in_up, in_down; + +int in_impulse; + + +void KeyDown (kbutton_t *b) +{ + int k; + const char *c; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + Con_Printf ("Three keys down for a button!\n"); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +void KeyUp (kbutton_t *b) +{ + int k; + const char *c; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + return; // some other key is still holding it down + + if (!(b->state & 1)) + return; // still up (this should not happen) + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_MLookUp (void) { + KeyUp(&in_mlook); + if ( !(in_mlook.state&1) && lookspring.value) + V_StartPitchDrift(); +} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} +void IN_ForwardDown(void) {KeyDown(&in_forward);} +void IN_ForwardUp(void) {KeyUp(&in_forward);} +void IN_BackDown(void) {KeyDown(&in_back);} +void IN_BackUp(void) {KeyUp(&in_back);} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {KeyDown(&in_moveright);} +void IN_MoverightUp(void) {KeyUp(&in_moveright);} + +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +void IN_AttackDown(void) {KeyDown(&in_attack);} +void IN_AttackUp(void) {KeyUp(&in_attack);} + +void IN_UseDown (void) {KeyDown(&in_use);} +void IN_UseUp (void) {KeyUp(&in_use);} +void IN_JumpDown (void) {KeyDown(&in_jump);} +void IN_JumpUp (void) {KeyUp(&in_jump);} + +void IN_Impulse (void) {in_impulse=Q_atoi(Cmd_Argv(1));} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val; + qboolean impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + val = 0; + + if (impulsedown && !impulseup) + { + if (down) + val = 0.5; // pressed and held this frame + else + val = 0; // I_Error (); + } + if (impulseup && !impulsedown) + { + if (down) + val = 0; // I_Error (); + else + val = 0; // released this frame + } + if (!impulsedown && !impulseup) + { + if (down) + val = 1.0; // held the entire frame + else + val = 0; // up the entire frame + } + if (impulsedown && impulseup) + { + if (down) + val = 0.75; // released and re-pressed this frame + else + val = 0.25; // pressed and released this frame + } + + key->state &= 1; // clear impulses + + return val; +} + + +//========================================================================== + +cvar_t cl_upspeed = {"cl_upspeed","200",CVAR_NONE}; +cvar_t cl_forwardspeed = {"cl_forwardspeed","200", CVAR_ARCHIVE}; +cvar_t cl_backspeed = {"cl_backspeed","200", CVAR_ARCHIVE}; +cvar_t cl_sidespeed = {"cl_sidespeed","350",CVAR_NONE}; + +cvar_t cl_movespeedkey = {"cl_movespeedkey","2.0",CVAR_NONE}; + +cvar_t cl_yawspeed = {"cl_yawspeed","140",CVAR_NONE}; +cvar_t cl_pitchspeed = {"cl_pitchspeed","150",CVAR_NONE}; + +cvar_t cl_anglespeedkey = {"cl_anglespeedkey","1.5",CVAR_NONE}; + +cvar_t cl_alwaysrun = {"cl_alwaysrun","0",CVAR_ARCHIVE}; // QuakeSpasm -- new always run + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles (void) +{ + float speed; + float up, down; + + if ((in_speed.state & 1) ^ (cl_alwaysrun.value != 0.0)) + speed = host_frametime * cl_anglespeedkey.value; + else + speed = host_frametime; + + if (!(in_strafe.state & 1)) + { + cl.viewangles[YAW] -= speed*cl_yawspeed.value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed.value*CL_KeyState (&in_left); + cl.viewangles[YAW] = anglemod(cl.viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * CL_KeyState (&in_forward); + cl.viewangles[PITCH] += speed*cl_pitchspeed.value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * up; + cl.viewangles[PITCH] += speed*cl_pitchspeed.value * down; + + if (up || down) + V_StopPitchDrift (); + + //johnfitz -- variable pitch clamping + if (cl.viewangles[PITCH] > cl_maxpitch.value) + cl.viewangles[PITCH] = cl_maxpitch.value; + if (cl.viewangles[PITCH] < cl_minpitch.value) + cl.viewangles[PITCH] = cl_minpitch.value; + //johnfitz + + if (cl.viewangles[ROLL] > 50) + cl.viewangles[ROLL] = 50; + if (cl.viewangles[ROLL] < -50) + cl.viewangles[ROLL] = -50; +} + +/* +================ +CL_BaseMove + +Send the intended movement message to the server +================ +*/ +void CL_BaseMove (usercmd_t *cmd) +{ + if (cls.signon != SIGNONS) + return; + + CL_AdjustAngles (); + + Q_memset (cmd, 0, sizeof(*cmd)); + + if (in_strafe.state & 1) + { + cmd->sidemove += cl_sidespeed.value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed.value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed.value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed.value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed.value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed.value * CL_KeyState (&in_down); + + if (! (in_klook.state & 1) ) + { + cmd->forwardmove += cl_forwardspeed.value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed.value * CL_KeyState (&in_back); + } + +// +// adjust for speed key +// + if ((in_speed.state & 1) ^ (cl_alwaysrun.value != 0.0)) + { + cmd->forwardmove *= cl_movespeedkey.value; + cmd->sidemove *= cl_movespeedkey.value; + cmd->upmove *= cl_movespeedkey.value; + } +} + + +/* +============== +CL_SendMove +============== +*/ +void CL_SendMove (const usercmd_t *cmd) +{ + int i; + int bits; + sizebuf_t buf; + byte data[128]; + + buf.maxsize = 128; + buf.cursize = 0; + buf.data = data; + + cl.cmd = *cmd; + +// +// send the movement message +// + MSG_WriteByte (&buf, clc_move); + + MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times + + for (i=0 ; i<3 ; i++) + //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE + if (cl.protocol == PROTOCOL_NETQUAKE) + MSG_WriteAngle (&buf, cl.viewangles[i], cl.protocolflags); + else + MSG_WriteAngle16 (&buf, cl.viewangles[i], cl.protocolflags); + //johnfitz + + MSG_WriteShort (&buf, cmd->forwardmove); + MSG_WriteShort (&buf, cmd->sidemove); + MSG_WriteShort (&buf, cmd->upmove); + +// +// send button bits +// + bits = 0; + + if ( in_attack.state & 3 ) + bits |= 1; + in_attack.state &= ~2; + + if (in_jump.state & 3) + bits |= 2; + in_jump.state &= ~2; + + MSG_WriteByte (&buf, bits); + + MSG_WriteByte (&buf, in_impulse); + in_impulse = 0; + +// +// deliver the message +// + if (cls.demoplayback) + return; + +// +// allways dump the first two message, because it may contain leftover inputs +// from the last level +// + if (++cl.movemessages <= 2) + return; + + if (NET_SendUnreliableMessage (cls.netcon, &buf) == -1) + { + Con_Printf ("CL_SendMove: lost server connection\n"); + CL_Disconnect (); + } +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput (void) +{ + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_AttackDown); + Cmd_AddCommand ("-attack", IN_AttackUp); + Cmd_AddCommand ("+use", IN_UseDown); + Cmd_AddCommand ("-use", IN_UseUp); + Cmd_AddCommand ("+jump", IN_JumpDown); + Cmd_AddCommand ("-jump", IN_JumpUp); + Cmd_AddCommand ("impulse", IN_Impulse); + Cmd_AddCommand ("+klook", IN_KLookDown); + Cmd_AddCommand ("-klook", IN_KLookUp); + Cmd_AddCommand ("+mlook", IN_MLookDown); + Cmd_AddCommand ("-mlook", IN_MLookUp); + +} + diff --git a/Quake/cl_main.cpp b/Quake/cl_main.cpp new file mode 100644 index 0000000..319010d --- /dev/null +++ b/Quake/cl_main.cpp @@ -0,0 +1,841 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_main.c -- client main loop + +#include "quakedef.hpp" +#include "bgmusic.hpp" + +// we need to declare some mouse variables here, because the menu system +// references them even when on a unix system. + +// these two are not intended to be set directly +cvar_t cl_name = {"_cl_name", "player", CVAR_ARCHIVE}; +cvar_t cl_color = {"_cl_color", "0", CVAR_ARCHIVE}; + +cvar_t cl_shownet = {"cl_shownet","0",CVAR_NONE}; // can be 0, 1, or 2 +cvar_t cl_nolerp = {"cl_nolerp","0",CVAR_NONE}; + +cvar_t cfg_unbindall = {"cfg_unbindall", "1", CVAR_ARCHIVE}; + +cvar_t lookspring = {"lookspring","0", CVAR_ARCHIVE}; +cvar_t lookstrafe = {"lookstrafe","0", CVAR_ARCHIVE}; +cvar_t sensitivity = {"sensitivity","3", CVAR_ARCHIVE}; + +cvar_t m_pitch = {"m_pitch","0.022", CVAR_ARCHIVE}; +cvar_t m_yaw = {"m_yaw","0.022", CVAR_ARCHIVE}; +cvar_t m_forward = {"m_forward","1", CVAR_ARCHIVE}; +cvar_t m_side = {"m_side","0.8", CVAR_ARCHIVE}; + +cvar_t cl_maxpitch = {"cl_maxpitch", "90", CVAR_ARCHIVE}; //johnfitz -- variable pitch clamping +cvar_t cl_minpitch = {"cl_minpitch", "-90", CVAR_ARCHIVE}; //johnfitz -- variable pitch clamping + +cvar_t cl_startdemos = {"cl_startdemos", "1", CVAR_ARCHIVE}; + +client_static_t cls; +client_state_t cl; +// FIXME: put these on hunk? +entity_t cl_static_entities[MAX_STATIC_ENTITIES]; +lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; +dlight_t cl_dlights[MAX_DLIGHTS]; + +entity_t *cl_entities; //johnfitz -- was a static array, now on hunk +int cl_max_edicts; //johnfitz -- only changes when new map loads + +int cl_numvisedicts; +entity_t *cl_visedicts[MAX_VISEDICTS]; + +extern cvar_t r_lerpmodels, r_lerpmove; //johnfitz + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_ClearState (void) +{ + if (!sv.active) + Host_ClearMemory (); + +// wipe the entire cl structure + memset (&cl, 0, sizeof(cl)); + + SZ_Clear (&cls.message); + +// clear other arrays + memset (cl_dlights, 0, sizeof(cl_dlights)); + memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); + memset (cl_temp_entities, 0, sizeof(cl_temp_entities)); + memset (cl_beams, 0, sizeof(cl_beams)); + + //johnfitz -- cl_entities is now dynamically allocated + cl_max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); + cl_entities = (entity_t *) Hunk_AllocName (cl_max_edicts*sizeof(entity_t), "cl_entities"); + //johnfitz +} + +/* +===================== +CL_Disconnect + +Sends a disconnect message to the server +This is also called on Host_Error, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect (void) +{ + if (key_dest == key_message) + Key_EndChat (); // don't get stuck in chat mode + +// stop sounds (especially looping!) + S_StopAllSounds (true); + BGM_Stop(); + CDAudio_Stop(); + +// if running a local server, shut it down + if (cls.demoplayback) + CL_StopPlayback (); + else if (cls.state == ca_connected) + { + if (cls.demorecording) + CL_Stop_f (); + + Con_DPrintf ("Sending clc_disconnect\n"); + SZ_Clear (&cls.message); + MSG_WriteByte (&cls.message, clc_disconnect); + NET_SendUnreliableMessage (cls.netcon, &cls.message); + SZ_Clear (&cls.message); + NET_Close (cls.netcon); + + cls.state = ca_disconnected; + if (sv.active) + Host_ShutdownServer(false); + } + + cls.demoplayback = cls.timedemo = false; + cls.demopaused = false; + cl.intermission = 0; + CL_ClearSignons (); +} + +void CL_Disconnect_f (void) +{ + CL_Disconnect (); + if (sv.active) + Host_ShutdownServer (false); +} + + +/* +===================== +CL_EstablishConnection + +Host should be either "local" or a net address to be passed on +===================== +*/ +void CL_EstablishConnection (const char *host) +{ + if (cls.state == ca_dedicated) + return; + + if (cls.demoplayback) + return; + + CL_Disconnect (); + + cls.netcon = NET_Connect (host); + if (!cls.netcon) + Host_Error ("CL_Connect: connect failed"); + Con_DPrintf ("CL_EstablishConnection: connected to %s\n", host); + + cls.demonum = -1; // not in the demo loop now + cls.state = ca_connected; + CL_ClearSignons (); // need all the signon messages before playing + MSG_WriteByte (&cls.message, clc_nop); // NAT Fix from ProQuake +} + +/* +===================== +CL_SignonReply + +An svc_signonnum has been received, perform a client side setup +===================== +*/ +void CL_SignonReply (void) +{ + char str[8192]; + + Con_DPrintf ("CL_SignonReply: %i\n", cls.signon); + + switch (cls.signon) + { + case 1: + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, "prespawn"); + break; + + case 2: + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, va("name \"%s\"\n", cl_name.string)); + + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, va("color %i %i\n", ((int)cl_color.value)>>4, ((int)cl_color.value)&15)); + + MSG_WriteByte (&cls.message, clc_stringcmd); + sprintf (str, "spawn %s", cls.spawnparms); + MSG_WriteString (&cls.message, str); + break; + + case 3: + MSG_WriteByte (&cls.message, clc_stringcmd); + MSG_WriteString (&cls.message, "begin"); + Cache_Report (); // print remaining memory + break; + + case 4: + SCR_EndLoadingPlaque (); // allow normal screen updates + break; + } +} + +/* +===================== +CL_NextDemo + +Called to play the next demo in the demo loop +===================== +*/ +void CL_NextDemo (void) +{ + char str[1024]; + + if (cls.demonum == -1) + return; // don't play demos + + if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS) + { + cls.demonum = 0; + if (!cls.demos[cls.demonum][0]) + { + Con_Printf ("No demos listed with startdemos\n"); + cls.demonum = -1; + CL_Disconnect(); + return; + } + } + + SCR_BeginLoadingPlaque (); + + sprintf (str,"playdemo %s\n", cls.demos[cls.demonum]); + Cbuf_InsertText (str); + cls.demonum++; +} + +/* +============== +CL_PrintEntities_f +============== +*/ +void CL_PrintEntities_f (void) +{ + entity_t *ent; + int i; + + if (cls.state != ca_connected) + return; + + for (i=0,ent=cl_entities ; imodel) + { + Con_Printf ("EMPTY\n"); + continue; + } + Con_Printf ("%s:%2i (%5.1f,%5.1f,%5.1f) [%5.1f %5.1f %5.1f]\n" + ,ent->model->name,ent->frame, ent->origin[0], ent->origin[1], ent->origin[2], ent->angles[0], ent->angles[1], ent->angles[2]); + } +} + +/* +=============== +CL_AllocDlight + +=============== +*/ +dlight_t *CL_AllocDlight (int key) +{ + int i; + dlight_t *dl; + +// first look for an exact key match + if (key) + { + dl = cl_dlights; + for (i=0 ; ikey == key) + { + memset (dl, 0, sizeof(*dl)); + dl->key = key; + dl->color[0] = dl->color[1] = dl->color[2] = 1; //johnfitz -- lit support via lordhavoc + return dl; + } + } + } + +// then look for anything else + dl = cl_dlights; + for (i=0 ; idie < cl.time) + { + memset (dl, 0, sizeof(*dl)); + dl->key = key; + dl->color[0] = dl->color[1] = dl->color[2] = 1; //johnfitz -- lit support via lordhavoc + return dl; + } + } + + dl = &cl_dlights[0]; + memset (dl, 0, sizeof(*dl)); + dl->key = key; + dl->color[0] = dl->color[1] = dl->color[2] = 1; //johnfitz -- lit support via lordhavoc + return dl; +} + + +/* +=============== +CL_DecayLights + +=============== +*/ +void CL_DecayLights (void) +{ + int i; + dlight_t *dl; + float time; + + time = cl.time - cl.oldtime; + + dl = cl_dlights; + for (i=0 ; idie < cl.time || !dl->radius) + continue; + + dl->radius -= time*dl->decay; + if (dl->radius < 0) + dl->radius = 0; + } +} + + +/* +=============== +CL_LerpPoint + +Determines the fraction between the last two messages that the objects +should be put at. +=============== +*/ +float CL_LerpPoint (void) +{ + float f, frac; + + f = cl.mtime[0] - cl.mtime[1]; + + if (!f || cls.timedemo || sv.active) + { + cl.time = cl.mtime[0]; + return 1; + } + + if (f > 0.1) // dropped packet, or start of demo + { + cl.mtime[1] = cl.mtime[0] - 0.1; + f = 0.1; + } + + frac = (cl.time - cl.mtime[1]) / f; + + if (frac < 0) + { + if (frac < -0.01) + cl.time = cl.mtime[1]; + frac = 0; + } + else if (frac > 1) + { + if (frac > 1.01) + cl.time = cl.mtime[0]; + frac = 1; + } + + //johnfitz -- better nolerp behavior + if (cl_nolerp.value) + return 1; + //johnfitz + + return frac; +} + +/* +=============== +CL_RelinkEntities +=============== +*/ +void CL_RelinkEntities (void) +{ + entity_t *ent; + int i, j; + float frac, f, d; + vec3_t delta; + float bobjrotate; + vec3_t oldorg; + dlight_t *dl; + +// determine partial update time + frac = CL_LerpPoint (); + + cl_numvisedicts = 0; + +// +// interpolate player info +// + for (i=0 ; i<3 ; i++) + cl.velocity[i] = cl.mvelocity[1][i] + + frac * (cl.mvelocity[0][i] - cl.mvelocity[1][i]); + + if (cls.demoplayback) + { + // interpolate the angles + for (j=0 ; j<3 ; j++) + { + d = cl.mviewangles[0][j] - cl.mviewangles[1][j]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + cl.viewangles[j] = cl.mviewangles[1][j] + frac*d; + } + } + + bobjrotate = anglemod(100*cl.time); + +// start on the entity after the world + for (i=1,ent=cl_entities+1 ; imodel) + { // empty slot + + // ericw -- efrags are only used for static entities in GLQuake + // ent can't be static, so this is a no-op. + //if (ent->forcelink) + // R_RemoveEfrags (ent); // just became empty + continue; + } + +// if the object wasn't included in the last packet, remove it + if (ent->msgtime != cl.mtime[0]) + { + ent->model = NULL; + ent->lerpflags |= LERP_RESETMOVE|LERP_RESETANIM; //johnfitz -- next time this entity slot is reused, the lerp will need to be reset + continue; + } + + VectorCopy (ent->origin, oldorg); + + if (ent->forcelink) + { // the entity was not updated in the last message + // so move to the final spot + VectorCopy (ent->msg_origins[0], ent->origin); + VectorCopy (ent->msg_angles[0], ent->angles); + } + else + { // if the delta is large, assume a teleport and don't lerp + f = frac; + for (j=0 ; j<3 ; j++) + { + delta[j] = ent->msg_origins[0][j] - ent->msg_origins[1][j]; + if (delta[j] > 100 || delta[j] < -100) + { + f = 1; // assume a teleportation, not a motion + ent->lerpflags |= LERP_RESETMOVE; //johnfitz -- don't lerp teleports + } + } + + //johnfitz -- don't cl_lerp entities that will be r_lerped + if (r_lerpmove.value && (ent->lerpflags & LERP_MOVESTEP)) + f = 1; + //johnfitz + + // interpolate the origin and angles + for (j=0 ; j<3 ; j++) + { + ent->origin[j] = ent->msg_origins[1][j] + f*delta[j]; + + d = ent->msg_angles[0][j] - ent->msg_angles[1][j]; + if (d > 180) + d -= 360; + else if (d < -180) + d += 360; + ent->angles[j] = ent->msg_angles[1][j] + f*d; + } + } + +// rotate binary objects locally + if (ent->model->flags & EF_ROTATE) + ent->angles[1] = bobjrotate; + + if (ent->effects & EF_BRIGHTFIELD) + R_EntityParticles (ent); + + if (ent->effects & EF_MUZZLEFLASH) + { + vec3_t fv, rv, uv; + + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->origin[2] += 16; + AngleVectors (ent->angles, fv, rv, uv); + + VectorMA (dl->origin, 18, fv, dl->origin); + dl->radius = 200 + (rand()&31); + dl->minlight = 32; + dl->die = cl.time + 0.1; + + //johnfitz -- assume muzzle flash accompanied by muzzle flare, which looks bad when lerped + if (r_lerpmodels.value != 2) + { + if (ent == &cl_entities[cl.viewentity]) + cl.viewent.lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames + else + ent->lerpflags |= LERP_RESETANIM|LERP_RESETANIM2; //no lerping for two frames + } + //johnfitz + } + if (ent->effects & EF_BRIGHTLIGHT) + { + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->origin[2] += 16; + dl->radius = 400 + (rand()&31); + dl->die = cl.time + 0.001; + } + if (ent->effects & EF_DIMLIGHT) + { + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->radius = 200 + (rand()&31); + dl->die = cl.time + 0.001; + } + if (ent->effects & EF_QEX_QUADLIGHT) + { + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->radius = 200 + (rand()&31); + dl->die = cl.time + 0.001; + dl->color[0] = 0.25f; + dl->color[1] = 0.25f; + dl->color[2] = 1.0f; + } + if (ent->effects & EF_QEX_PENTALIGHT) + { + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->radius = 200 + (rand()&31); + dl->die = cl.time + 0.001; + dl->color[0] = 1.0f; + dl->color[1] = 0.25f; + dl->color[2] = 0.25f; + } + + if (ent->model->flags & EF_GIB) + R_RocketTrail (oldorg, ent->origin, 2); + else if (ent->model->flags & EF_ZOMGIB) + R_RocketTrail (oldorg, ent->origin, 4); + else if (ent->model->flags & EF_TRACER) + R_RocketTrail (oldorg, ent->origin, 3); + else if (ent->model->flags & EF_TRACER2) + R_RocketTrail (oldorg, ent->origin, 5); + else if (ent->model->flags & EF_ROCKET) + { + R_RocketTrail (oldorg, ent->origin, 0); + dl = CL_AllocDlight (i); + VectorCopy (ent->origin, dl->origin); + dl->radius = 200; + dl->die = cl.time + 0.01; + } + else if (ent->model->flags & EF_GRENADE) + R_RocketTrail (oldorg, ent->origin, 1); + else if (ent->model->flags & EF_TRACER3) + R_RocketTrail (oldorg, ent->origin, 6); + + ent->forcelink = false; + + if (i == cl.viewentity && !chase_active.value) + continue; + + if (cl_numvisedicts < MAX_VISEDICTS) + { + cl_visedicts[cl_numvisedicts] = ent; + cl_numvisedicts++; + } + } +} + + +/* +=============== +CL_ReadFromServer + +Read all incoming data from the server +=============== +*/ +int CL_ReadFromServer (void) +{ + int ret; + extern int num_temp_entities; //johnfitz + int num_beams = 0; //johnfitz + int num_dlights = 0; //johnfitz + beam_t *b; //johnfitz + dlight_t *l; //johnfitz + int i; //johnfitz + + + cl.oldtime = cl.time; + cl.time += host_frametime; + + do + { + ret = CL_GetMessage (); + if (ret == -1) + Host_Error ("CL_ReadFromServer: lost server connection"); + if (!ret) + break; + + cl.last_received_message = realtime; + CL_ParseServerMessage (); + } while (ret && cls.state == ca_connected); + + if (cl_shownet.value) + Con_Printf ("\n"); + + CL_RelinkEntities (); + CL_UpdateTEnts (); + +//johnfitz -- devstats + + //visedicts + if (cl_numvisedicts > 256 && dev_peakstats.visedicts <= 256) + Con_DWarning ("%i visedicts exceeds standard limit of 256 (max = %d).\n", cl_numvisedicts, MAX_VISEDICTS); + dev_stats.visedicts = cl_numvisedicts; + dev_peakstats.visedicts = q_max(cl_numvisedicts, dev_peakstats.visedicts); + + //temp entities + if (num_temp_entities > 64 && dev_peakstats.tempents <= 64) + Con_DWarning ("%i tempentities exceeds standard limit of 64 (max = %d).\n", num_temp_entities, MAX_TEMP_ENTITIES); + dev_stats.tempents = num_temp_entities; + dev_peakstats.tempents = q_max(num_temp_entities, dev_peakstats.tempents); + + //beams + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->model && b->endtime >= cl.time) + num_beams++; + if (num_beams > 24 && dev_peakstats.beams <= 24) + Con_DWarning ("%i beams exceeded standard limit of 24 (max = %d).\n", num_beams, MAX_BEAMS); + dev_stats.beams = num_beams; + dev_peakstats.beams = q_max(num_beams, dev_peakstats.beams); + + //dlights + for (i=0, l=cl_dlights ; idie >= cl.time && l->radius) + num_dlights++; + if (num_dlights > 32 && dev_peakstats.dlights <= 32) + Con_DWarning ("%i dlights exceeded standard limit of 32 (max = %d).\n", num_dlights, MAX_DLIGHTS); + dev_stats.dlights = num_dlights; + dev_peakstats.dlights = q_max(num_dlights, dev_peakstats.dlights); + +//johnfitz + +// +// bring the links up to date +// + return 0; +} + +/* +================= +CL_SendCmd +================= +*/ +void CL_SendCmd (void) +{ + usercmd_t cmd; + + if (cls.state != ca_connected) + return; + + if (cls.signon == SIGNONS) + { + // get basic movement from keyboard + CL_BaseMove (&cmd); + + // allow mice or other external controllers to add to the move + IN_Move (&cmd); + + // send the unreliable message + CL_SendMove (&cmd); + } + + if (cls.demoplayback) + { + SZ_Clear (&cls.message); + return; + } + +// send the reliable message + if (!cls.message.cursize) + return; // no message at all + + if (!NET_CanSendMessage (cls.netcon)) + { + Con_DPrintf ("CL_SendCmd: can't send\n"); + return; + } + + if (NET_SendMessage (cls.netcon, &cls.message) == -1) + Host_Error ("CL_SendCmd: lost server connection"); + + SZ_Clear (&cls.message); +} + +/* +============= +CL_Tracepos_f -- johnfitz + +display impact point of trace along VPN +============= +*/ +void CL_Tracepos_f (void) +{ + vec3_t v, w; + + if (cls.state != ca_connected) + return; + + VectorMA(r_refdef.vieworg, 8192.0, vpn, v); + TraceLine(r_refdef.vieworg, v, w); + + if (VectorLength(w) == 0) + Con_Printf ("Tracepos: trace didn't hit anything\n"); + else + Con_Printf ("Tracepos: (%i %i %i)\n", (int)w[0], (int)w[1], (int)w[2]); +} + +/* +============= +CL_Viewpos_f -- johnfitz + +display client's position and angles +============= +*/ +void CL_Viewpos_f (void) +{ + if (cls.state != ca_connected) + return; +#if 0 + //camera position + Con_Printf ("Viewpos: (%i %i %i) %i %i %i\n", + (int)r_refdef.vieworg[0], + (int)r_refdef.vieworg[1], + (int)r_refdef.vieworg[2], + (int)r_refdef.viewangles[PITCH], + (int)r_refdef.viewangles[YAW], + (int)r_refdef.viewangles[ROLL]); +#else + //player position + Con_Printf ("Viewpos: (%i %i %i) %i %i %i\n", + (int)cl_entities[cl.viewentity].origin[0], + (int)cl_entities[cl.viewentity].origin[1], + (int)cl_entities[cl.viewentity].origin[2], + (int)cl.viewangles[PITCH], + (int)cl.viewangles[YAW], + (int)cl.viewangles[ROLL]); +#endif +} + +/* +================= +CL_Init +================= +*/ +void CL_Init (void) +{ + SZ_Alloc (&cls.message, 1024); + + CL_InitInput (); + CL_InitTEnts (); + + Cvar_RegisterVariable (&cl_name); + Cvar_RegisterVariable (&cl_color); + Cvar_RegisterVariable (&cl_upspeed); + Cvar_RegisterVariable (&cl_forwardspeed); + Cvar_RegisterVariable (&cl_backspeed); + Cvar_RegisterVariable (&cl_sidespeed); + Cvar_RegisterVariable (&cl_movespeedkey); + Cvar_RegisterVariable (&cl_yawspeed); + Cvar_RegisterVariable (&cl_pitchspeed); + Cvar_RegisterVariable (&cl_anglespeedkey); + Cvar_RegisterVariable (&cl_shownet); + Cvar_RegisterVariable (&cl_nolerp); + Cvar_RegisterVariable (&lookspring); + Cvar_RegisterVariable (&lookstrafe); + Cvar_RegisterVariable (&sensitivity); + + Cvar_RegisterVariable (&cl_alwaysrun); + + Cvar_RegisterVariable (&m_pitch); + Cvar_RegisterVariable (&m_yaw); + Cvar_RegisterVariable (&m_forward); + Cvar_RegisterVariable (&m_side); + + Cvar_RegisterVariable (&cfg_unbindall); + + Cvar_RegisterVariable (&cl_maxpitch); //johnfitz -- variable pitch clamping + Cvar_RegisterVariable (&cl_minpitch); //johnfitz -- variable pitch clamping + + Cvar_RegisterVariable (&cl_startdemos); + + Cmd_AddCommand ("entities", CL_PrintEntities_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("stop", CL_Stop_f); + Cmd_AddCommand ("playdemo", CL_PlayDemo_f); + Cmd_AddCommand ("timedemo", CL_TimeDemo_f); + + Cmd_AddCommand ("tracepos", CL_Tracepos_f); //johnfitz + Cmd_AddCommand ("viewpos", CL_Viewpos_f); //johnfitz +} + diff --git a/Quake/cl_parse.cpp b/Quake/cl_parse.cpp new file mode 100644 index 0000000..0e31f55 --- /dev/null +++ b/Quake/cl_parse.cpp @@ -0,0 +1,1260 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_parse.c -- parse a message received from the server + +#include + +#include "quakedef.hpp" +#include "bgmusic.hpp" + +const char *svc_strings[] = +{ + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_updatestat", + "svc_version", // [long] server version + "svc_setview", // [short] entity number + "svc_sound", // + "svc_time", // [float] server time + "svc_print", // [string] null terminated string + "svc_stufftext", // [string] stuffed into client's console buffer + // the string should be \n terminated + "svc_setangle", // [vec3] set the view angle to this absolute value + + "svc_serverinfo", // [long] version + // [string] signon string + // [string]..[0]model cache [string]...[0]sounds cache + // [string]..[0]item cache + "svc_lightstyle", // [byte] [string] + "svc_updatename", // [byte] [string] + "svc_updatefrags", // [byte] [short] + "svc_clientdata", // + "svc_stopsound", // + "svc_updatecolors", // [byte] [byte] + "svc_particle", // [vec3] + "svc_damage", // [byte] impact [byte] blood [vec3] from + + "svc_spawnstatic", + "OBSOLETE svc_spawnbinary", + "svc_spawnbaseline", + + "svc_temp_entity", // + "svc_setpause", + "svc_signonnum", + "svc_centerprint", + "svc_killedmonster", + "svc_foundsecret", + "svc_spawnstaticsound", + "svc_intermission", + "svc_finale", // [string] music [string] text + "svc_cdtrack", // [byte] track [byte] looptrack + "svc_sellscreen", + "svc_cutscene", + //johnfitz -- new server messages + "", // 35 + "", // 36 + "svc_skybox", // 37 // [string] skyname + "svc_botchat", // 38 (2021 RE-RELEASE) + "", // 39 + "svc_bf", // 40 // no data + "svc_fog", // 41 // [byte] density [byte] red [byte] green [byte] blue [float] time + "svc_spawnbaseline2", //42 // support for large modelindex, large framenum, alpha, using flags + "svc_spawnstatic2", // 43 // support for large modelindex, large framenum, alpha, using flags + "svc_spawnstaticsound2", // 44 // [coord3] [short] samp [byte] vol [byte] aten + //johnfitz + + // 2021 RE-RELEASE: + "svc_setviews", // 45 + "svc_updateping", // 46 + "svc_updatesocial", // 47 + "svc_updateplinfo", // 48 + "svc_rawprint", // 49 + "svc_servervars", // 50 + "svc_seq", // 51 + "svc_achievement", // 52 + "svc_chat", // 53 + "svc_levelcompleted", // 54 + "svc_backtolobby", // 55 + "svc_localsound" // 56 +}; +#define NUM_SVC_STRINGS Q_COUNTOF(svc_strings) + +qboolean warn_about_nehahra_protocol; //johnfitz + +extern vec3_t v_punchangles[2]; //johnfitz + +//============================================================================= + +/* +=============== +CL_EntityNum + +This error checks and tracks the total number of entities +=============== +*/ +entity_t *CL_EntityNum(int num) { + //johnfitz -- check minimum number too + if (num < 0) + Host_Error("CL_EntityNum: %i is an invalid number", num); + //john + + if (num >= cl.num_entities) { + if (num >= cl_max_edicts) //johnfitz -- no more MAX_EDICTS + Host_Error("CL_EntityNum: %i is an invalid number", num); + while (cl.num_entities <= num) { + cl_entities[cl.num_entities].colormap = vid.colormap; + cl_entities[cl.num_entities].lerpflags |= LERP_RESETMOVE | LERP_RESETANIM; //johnfitz + cl_entities[cl.num_entities].baseline.scale = ENTSCALE_DEFAULT; + cl.num_entities++; + } + } + + return &cl_entities[num]; +} + + +/* +================== +CL_ParseStartSoundPacket +================== +*/ +void CL_ParseStartSoundPacket(void) { + vec3_t pos; + int channel, ent; + int sound_num; + int volume; + int field_mask; + float attenuation; + int i; + + field_mask = MSG_ReadByte(); + + if (field_mask & SND_VOLUME) + volume = MSG_ReadByte(); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (field_mask & SND_ATTENUATION) + attenuation = MSG_ReadByte() / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (field_mask & SND_LARGEENTITY) { + ent = (unsigned short) MSG_ReadShort(); + channel = MSG_ReadByte(); + } else { + channel = (unsigned short) MSG_ReadShort(); + ent = channel >> 3; + channel &= 7; + } + + if (field_mask & SND_LARGESOUND) + sound_num = (unsigned short) MSG_ReadShort(); + else + sound_num = MSG_ReadByte(); + //johnfitz + + //johnfitz -- check soundnum + if (sound_num >= MAX_SOUNDS) + Host_Error("CL_ParseStartSoundPacket: %i > MAX_SOUNDS", sound_num); + //johnfitz + + if (ent > cl_max_edicts) //johnfitz -- no more MAX_EDICTS + Host_Error("CL_ParseStartSoundPacket: ent = %i", ent); + + for (i = 0; i < 3; i++) + pos[i] = MSG_ReadCoord(cl.protocolflags); + + S_StartSound(ent, channel, cl.sound_precache[sound_num], pos, volume / 255.0, attenuation); +} + +/* +================== +CL_ParseLocalSound - for 2021 rerelease +================== +*/ +void CL_ParseLocalSound(void) { + int field_mask, sound_num; + + field_mask = MSG_ReadByte(); + sound_num = (field_mask & SND_LARGESOUND) ? MSG_ReadShort() : MSG_ReadByte(); + if (sound_num >= MAX_SOUNDS) + Host_Error("CL_ParseLocalSound: %i > MAX_SOUNDS", sound_num); + + S_LocalSound(cl.sound_precache[sound_num]->name); +} + +/* +================== +CL_KeepaliveMessage + +When the client is taking a long time to load stuff, send keepalive messages +so the server doesn't disconnect. +================== +*/ +static byte net_olddata[NET_MAXMESSAGE]; + +void CL_KeepaliveMessage(void) { + float time; + static float lastmsg; + int ret; + sizebuf_t old; + byte *olddata; + + if (sv.active) + return; // no need if server is local + if (cls.demoplayback) + return; + + // read messages from server, should just be nops + olddata = net_olddata; + old = net_message; + memcpy(olddata, net_message.data, net_message.cursize); + + do { + ret = CL_GetMessage(); + switch (ret) { + default: + Host_Error("CL_KeepaliveMessage: CL_GetMessage failed"); + case 0: + break; // nothing waiting + case 1: + Host_Error("CL_KeepaliveMessage: received a message"); + break; + case 2: + if (MSG_ReadByte() != svc_nop) + Host_Error("CL_KeepaliveMessage: datagram wasn't a nop"); + break; + } + } while (ret); + + net_message = old; + memcpy(net_message.data, olddata, net_message.cursize); + + // check time + time = Sys_DoubleTime(); + if (time - lastmsg < 5) + return; + lastmsg = time; + + // write out a nop + Con_Printf("--> client to server keepalive\n"); + + MSG_WriteByte(&cls.message, clc_nop); + NET_SendMessage(cls.netcon, &cls.message); + SZ_Clear(&cls.message); +} + +/* +================== +CL_ParseServerInfo +================== +*/ +void CL_ParseServerInfo(void) { + const char *str; + int i; + int nummodels, numsounds; + char model_precache[MAX_MODELS][MAX_QPATH]; + char sound_precache[MAX_SOUNDS][MAX_QPATH]; + + Con_DPrintf("Serverinfo packet received.\n"); + + // ericw -- bring up loading plaque for map changes within a demo. + // it will be hidden in CL_SignonReply. + if (cls.demoplayback) + SCR_BeginLoadingPlaque(); + + // + // wipe the client_state_t struct + // + CL_ClearState(); + + // parse protocol version number + i = MSG_ReadLong(); + //johnfitz -- support multiple protocols + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) { + Con_Printf("\n"); //because there's no newline after serverinfo print + Host_Error("Server returned version %i, not %i or %i or %i", i, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, + PROTOCOL_RMQ); + } + cl.protocol = i; + //johnfitz + + if (cl.protocol == PROTOCOL_RMQ) { + const unsigned int supportedflags = ( + PRFL_SHORTANGLE | PRFL_FLOATANGLE | PRFL_24BITCOORD | PRFL_FLOATCOORD | PRFL_EDICTSCALE | PRFL_INT32COORD); + + // mh - read protocol flags from server so that we know what protocol features to expect + cl.protocolflags = (unsigned int) MSG_ReadLong(); + + if (0 != (cl.protocolflags & (~supportedflags))) { + Con_Warning("PROTOCOL_RMQ protocolflags %i contains unsupported flags\n", cl.protocolflags); + } + } else cl.protocolflags = 0; + + // parse maxclients + cl.maxclients = MSG_ReadByte(); + if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) { + Host_Error("Bad maxclients (%u) from server", cl.maxclients); + } + cl.scores = (scoreboard_t *) Hunk_AllocName(cl.maxclients * sizeof(*cl.scores), "scores"); + + // parse gametype + cl.gametype = MSG_ReadByte(); + + // parse signon message + str = MSG_ReadString(); + std::strncpy(cl.levelname, str, sizeof(cl.levelname)); + + // seperate the printfs so the server message can have a color + Con_Printf("\n%s\n", Con_Quakebar(40)); //johnfitz + Con_Printf("%c%s\n", 2, str); + + //johnfitz -- tell user which protocol this is + Con_Printf("Using protocol %i\n", i); + + // first we go through and touch all of the precache data that still + // happens to be in the cache, so precaching something else doesn't + // needlessly purge it + + // precache models + memset(cl.model_precache, 0, sizeof(cl.model_precache)); + for (nummodels = 1; ; nummodels++) { + str = MSG_ReadString(); + if (!str[0]) + break; + if (nummodels == MAX_MODELS) { + Host_Error("Server sent too many model precaches"); + } + std::strncpy(model_precache[nummodels], str, MAX_QPATH); + Mod_TouchModel(str); + } + + //johnfitz -- check for excessive models + if (nummodels >= 256) + Con_DWarning("%i models exceeds standard limit of 256 (max = %d).\n", nummodels, MAX_MODELS); + //johnfitz + + // precache sounds + memset(cl.sound_precache, 0, sizeof(cl.sound_precache)); + for (numsounds = 1; ; numsounds++) { + str = MSG_ReadString(); + if (!str[0]) + break; + if (numsounds == MAX_SOUNDS) { + Host_Error("Server sent too many sound precaches"); + } + std::strncpy(sound_precache[numsounds], str, MAX_QPATH); + S_TouchSound(str); + } + + //johnfitz -- check for excessive sounds + if (numsounds >= 256) + Con_DWarning("%i sounds exceeds standard limit of 256 (max = %d).\n", numsounds, MAX_SOUNDS); + //johnfitz + + // + // now we try to load everything else until a cache allocation fails + // + + // copy the naked name of the map file to the cl structure -- O.S + COM_StripExtension(COM_SkipPath(model_precache[1]), cl.mapname, sizeof(cl.mapname)); + + for (i = 1; i < nummodels; i++) { + cl.model_precache[i] = Mod_ForName(model_precache[i], false); + if (cl.model_precache[i] == NULL) { + Host_Error("Model %s not found", model_precache[i]); + } + CL_KeepaliveMessage(); + } + + S_BeginPrecaching(); + for (i = 1; i < numsounds; i++) { + cl.sound_precache[i] = S_PrecacheSound(sound_precache[i]); + CL_KeepaliveMessage(); + } + S_EndPrecaching(); + + // local state + cl_entities[0].model = cl.worldmodel = cl.model_precache[1]; + + R_NewMap(); + + //johnfitz -- clear out string; we don't consider identical + //messages to be duplicates if the map has changed in between + con_lastcenterstring[0] = 0; + //johnfitz + + Hunk_Check(); // make sure nothing is hurt + + noclip_anglehack = false; // noclip is turned off at start + + warn_about_nehahra_protocol = true; //johnfitz -- warn about nehahra protocol hack once per server connection + + //johnfitz -- reset developer stats + memset(&dev_stats, 0, sizeof(dev_stats)); + memset(&dev_peakstats, 0, sizeof(dev_peakstats)); + memset(&dev_overflows, 0, sizeof(dev_overflows)); +} + +/* +================== +CL_ParseUpdate + +Parse an entity update message from the server +If an entities model or origin changes from frame to frame, it must be +relinked. Other attributes can change without relinking. +================== +*/ +void CL_ParseUpdate(int bits) { + int i; + qmodel_t *model; + int modnum; + qboolean forcelink; + entity_t *ent; + int num; + int skin; + + if (cls.signon == SIGNONS - 1) { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply(); + } + + if (bits & U_MOREBITS) { + i = MSG_ReadByte(); + bits |= (i << 8); + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (cl.protocol == PROTOCOL_FITZQUAKE || cl.protocol == PROTOCOL_RMQ) { + if (bits & U_EXTEND1) + bits |= MSG_ReadByte() << 16; + if (bits & U_EXTEND2) + bits |= MSG_ReadByte() << 24; + } + //johnfitz + + if (bits & U_LONGENTITY) + num = MSG_ReadShort(); + else + num = MSG_ReadByte(); + + ent = CL_EntityNum(num); + + if (ent->msgtime != cl.mtime[1]) + forcelink = true; // no previous frame to lerp from + else + forcelink = false; + + //johnfitz -- lerping + if (ent->msgtime + 0.2 < cl.mtime[0]) + //more than 0.2 seconds since the last message (most entities think every 0.1 sec) + ent->lerpflags |= LERP_RESETANIM; //if we missed a think, we'd be lerping from the wrong frame + //johnfitz + + ent->msgtime = cl.mtime[0]; + + if (bits & U_MODEL) { + modnum = MSG_ReadByte(); + if (modnum >= MAX_MODELS) + Host_Error("CL_ParseModel: bad modnum"); + } else + modnum = ent->baseline.modelindex; + + if (bits & U_FRAME) + ent->frame = MSG_ReadByte(); + else + ent->frame = ent->baseline.frame; + + if (bits & U_COLORMAP) + i = MSG_ReadByte(); + else + i = ent->baseline.colormap; + if (!i) + ent->colormap = vid.colormap; + else { + if (i > cl.maxclients) + Sys_Error("i >= cl.maxclients"); + ent->colormap = cl.scores[i - 1].translations; + } + if (bits & U_SKIN) + skin = MSG_ReadByte(); + else + skin = ent->baseline.skin; + if (skin != ent->skinnum) { + ent->skinnum = skin; + if (num > 0 && num <= cl.maxclients) + R_TranslateNewPlayerSkin(num - 1); //johnfitz -- was R_TranslatePlayerSkin + } + if (bits & U_EFFECTS) + ent->effects = MSG_ReadByte(); + else + ent->effects = ent->baseline.effects; + + // shift the known values for interpolation + VectorCopy(ent->msg_origins[0], ent->msg_origins[1]); + VectorCopy(ent->msg_angles[0], ent->msg_angles[1]); + + if (bits & U_ORIGIN1) + ent->msg_origins[0][0] = MSG_ReadCoord(cl.protocolflags); + else + ent->msg_origins[0][0] = ent->baseline.origin[0]; + if (bits & U_ANGLE1) + ent->msg_angles[0][0] = MSG_ReadAngle(cl.protocolflags); + else + ent->msg_angles[0][0] = ent->baseline.angles[0]; + + if (bits & U_ORIGIN2) + ent->msg_origins[0][1] = MSG_ReadCoord(cl.protocolflags); + else + ent->msg_origins[0][1] = ent->baseline.origin[1]; + if (bits & U_ANGLE2) + ent->msg_angles[0][1] = MSG_ReadAngle(cl.protocolflags); + else + ent->msg_angles[0][1] = ent->baseline.angles[1]; + + if (bits & U_ORIGIN3) + ent->msg_origins[0][2] = MSG_ReadCoord(cl.protocolflags); + else + ent->msg_origins[0][2] = ent->baseline.origin[2]; + if (bits & U_ANGLE3) + ent->msg_angles[0][2] = MSG_ReadAngle(cl.protocolflags); + else + ent->msg_angles[0][2] = ent->baseline.angles[2]; + + //johnfitz -- lerping for movetype_step entities + if (bits & U_STEP) { + ent->lerpflags |= LERP_MOVESTEP; + ent->forcelink = true; + } else + ent->lerpflags &= ~LERP_MOVESTEP; + //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE and PROTOCOL_NEHAHRA + if (cl.protocol == PROTOCOL_FITZQUAKE || cl.protocol == PROTOCOL_RMQ) { + if (bits & U_ALPHA) + ent->alpha = MSG_ReadByte(); + else + ent->alpha = ent->baseline.alpha; + if (bits & U_SCALE) + ent->scale = MSG_ReadByte(); + else + ent->scale = ent->baseline.scale; + if (bits & U_FRAME2) + ent->frame = (ent->frame & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_MODEL2) + modnum = (modnum & 0x00FF) | (MSG_ReadByte() << 8); + if (bits & U_LERPFINISH) { + ent->lerpfinish = ent->msgtime + ((float) (MSG_ReadByte()) / 255); + ent->lerpflags |= LERP_FINISH; + } else + ent->lerpflags &= ~LERP_FINISH; + } else if (cl.protocol == PROTOCOL_NETQUAKE) { + //HACK: if this bit is set, assume this is PROTOCOL_NEHAHRA + if (bits & U_TRANS) { + float a, b; + + if (warn_about_nehahra_protocol) { + Con_Warning("nonstandard update bit, assuming Nehahra protocol\n"); + warn_about_nehahra_protocol = false; + } + + a = MSG_ReadFloat(); + b = MSG_ReadFloat(); //alpha + if (a == 2) + MSG_ReadFloat(); //fullbright (not using this yet) + ent->alpha = ENTALPHA_ENCODE(b); + } else + ent->alpha = ent->baseline.alpha; + ent->scale = ent->baseline.scale; + } + //johnfitz + + //johnfitz -- moved here from above + model = cl.model_precache[modnum]; + if (model != ent->model) { + ent->model = model; + // automatic animation (torches, etc) can be either all together + // or randomized + if (model) { + if (model->synctype == ST_RAND) + ent->syncbase = (float) (rand() & 0x7fff) / 0x7fff; + else + ent->syncbase = 0.0; + } else + forcelink = true; // hack to make null model players work + if (num > 0 && num <= cl.maxclients) + R_TranslateNewPlayerSkin(num - 1); //johnfitz -- was R_TranslatePlayerSkin + + ent->lerpflags |= LERP_RESETANIM; //johnfitz -- don't lerp animation across model changes + } + //johnfitz + + if (forcelink) { + // didn't have an update last message + VectorCopy(ent->msg_origins[0], ent->msg_origins[1]); + VectorCopy(ent->msg_origins[0], ent->origin); + VectorCopy(ent->msg_angles[0], ent->msg_angles[1]); + VectorCopy(ent->msg_angles[0], ent->angles); + ent->forcelink = true; + } +} + +/* +================== +CL_ParseBaseline +================== +*/ +void CL_ParseBaseline(entity_t *ent, int version) //johnfitz -- added argument +{ + int i; + int bits; //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE + bits = (version == 2) ? MSG_ReadByte() : 0; + ent->baseline.modelindex = (bits & B_LARGEMODEL) ? MSG_ReadShort() : MSG_ReadByte(); + ent->baseline.frame = (bits & B_LARGEFRAME) ? MSG_ReadShort() : MSG_ReadByte(); + //johnfitz + + ent->baseline.colormap = MSG_ReadByte(); + ent->baseline.skin = MSG_ReadByte(); + for (i = 0; i < 3; i++) { + ent->baseline.origin[i] = MSG_ReadCoord(cl.protocolflags); + ent->baseline.angles[i] = MSG_ReadAngle(cl.protocolflags); + } + + ent->baseline.alpha = (bits & B_ALPHA) ? MSG_ReadByte() : ENTALPHA_DEFAULT; //johnfitz -- PROTOCOL_FITZQUAKE + ent->baseline.scale = (bits & B_SCALE) ? MSG_ReadByte() : ENTSCALE_DEFAULT; +} + + +/* +================== +CL_ParseClientdata + +Server information pertaining to this client only +================== +*/ +void CL_ParseClientdata(void) { + int i, j; + int bits; //johnfitz + + bits = (unsigned short) MSG_ReadShort(); //johnfitz -- read bits here isntead of in CL_ParseServerMessage() + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & SU_EXTEND1) + bits |= (MSG_ReadByte() << 16); + if (bits & SU_EXTEND2) + bits |= (MSG_ReadByte() << 24); + //johnfitz + + if (bits & SU_VIEWHEIGHT) + cl.viewheight = MSG_ReadChar(); + else + cl.viewheight = DEFAULT_VIEWHEIGHT; + + if (bits & SU_IDEALPITCH) + cl.idealpitch = MSG_ReadChar(); + else + cl.idealpitch = 0; + + VectorCopy(cl.mvelocity[0], cl.mvelocity[1]); + for (i = 0; i < 3; i++) { + if (bits & (SU_PUNCH1 << i)) + cl.punchangle[i] = MSG_ReadChar(); + else + cl.punchangle[i] = 0; + + if (bits & (SU_VELOCITY1 << i)) + cl.mvelocity[0][i] = MSG_ReadChar() * 16; + else + cl.mvelocity[0][i] = 0; + } + + //johnfitz -- update v_punchangles + if (v_punchangles[0][0] != cl.punchangle[0] || v_punchangles[0][1] != cl.punchangle[1] || v_punchangles[0][2] != cl. + punchangle[2]) { + VectorCopy(v_punchangles[0], v_punchangles[1]); + VectorCopy(cl.punchangle, v_punchangles[0]); + } + //johnfitz + + // [always sent] if (bits & SU_ITEMS) + i = MSG_ReadLong(); + + if (cl.items != i) { + // set flash times + Sbar_Changed(); + for (j = 0; j < 32; j++) + if ((i & (1 << j)) && !(cl.items & (1 << j))) + cl.item_gettime[j] = cl.time; + cl.items = i; + } + + cl.onground = (bits & SU_ONGROUND) != 0; + cl.inwater = (bits & SU_INWATER) != 0; + + if (bits & SU_WEAPONFRAME) + cl.stats[STAT_WEAPONFRAME] = MSG_ReadByte(); + else + cl.stats[STAT_WEAPONFRAME] = 0; + + if (bits & SU_ARMOR) + i = MSG_ReadByte(); + else + i = 0; + if (cl.stats[STAT_ARMOR] != i) { + cl.stats[STAT_ARMOR] = i; + Sbar_Changed(); + } + + if (bits & SU_WEAPON) + i = MSG_ReadByte(); + else + i = 0; + if (cl.stats[STAT_WEAPON] != i) { + cl.stats[STAT_WEAPON] = i; + Sbar_Changed(); + } + + i = MSG_ReadShort(); + if (cl.stats[STAT_HEALTH] != i) { + cl.stats[STAT_HEALTH] = i; + Sbar_Changed(); + } + + i = MSG_ReadByte(); + if (cl.stats[STAT_AMMO] != i) { + cl.stats[STAT_AMMO] = i; + Sbar_Changed(); + } + + for (i = 0; i < 4; i++) { + j = MSG_ReadByte(); + if (cl.stats[STAT_SHELLS + i] != j) { + cl.stats[STAT_SHELLS + i] = j; + Sbar_Changed(); + } + } + + i = MSG_ReadByte(); + + if (standard_quake) { + if (cl.stats[STAT_ACTIVEWEAPON] != i) { + cl.stats[STAT_ACTIVEWEAPON] = i; + Sbar_Changed(); + } + } else { + if (cl.stats[STAT_ACTIVEWEAPON] != (1 << i)) { + cl.stats[STAT_ACTIVEWEAPON] = (1 << i); + Sbar_Changed(); + } + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & SU_WEAPON2) + cl.stats[STAT_WEAPON] |= (MSG_ReadByte() << 8); + if (bits & SU_ARMOR2) + cl.stats[STAT_ARMOR] |= (MSG_ReadByte() << 8); + if (bits & SU_AMMO2) + cl.stats[STAT_AMMO] |= (MSG_ReadByte() << 8); + if (bits & SU_SHELLS2) + cl.stats[STAT_SHELLS] |= (MSG_ReadByte() << 8); + if (bits & SU_NAILS2) + cl.stats[STAT_NAILS] |= (MSG_ReadByte() << 8); + if (bits & SU_ROCKETS2) + cl.stats[STAT_ROCKETS] |= (MSG_ReadByte() << 8); + if (bits & SU_CELLS2) + cl.stats[STAT_CELLS] |= (MSG_ReadByte() << 8); + if (bits & SU_WEAPONFRAME2) + cl.stats[STAT_WEAPONFRAME] |= (MSG_ReadByte() << 8); + if (bits & SU_WEAPONALPHA) + cl.viewent.alpha = MSG_ReadByte(); + else + cl.viewent.alpha = ENTALPHA_DEFAULT; + //johnfitz + + //johnfitz -- lerping + //ericw -- this was done before the upper 8 bits of cl.stats[STAT_WEAPON] were filled in, breaking on large maps like zendar.bsp + if (cl.viewent.model != cl.model_precache[cl.stats[STAT_WEAPON]]) { + cl.viewent.lerpflags |= LERP_RESETANIM; //don't lerp animation across model changes + } + //johnfitz +} + +/* +===================== +CL_NewTranslation +===================== +*/ +void CL_NewTranslation(int slot) { + int i, j; + int top, bottom; + byte *dest, *source; + + if (slot > cl.maxclients) + Sys_Error("CL_NewTranslation: slot > cl.maxclients"); + dest = cl.scores[slot].translations; + source = vid.colormap; + memcpy(dest, vid.colormap, sizeof(cl.scores[slot].translations)); + top = cl.scores[slot].colors & 0xf0; + bottom = (cl.scores[slot].colors & 15) << 4; + R_TranslatePlayerSkin(slot); + + for (i = 0; i < VID_GRADES; i++, dest += 256, source += 256) { + if (top < 128) // the artists made some backwards ranges. sigh. + memcpy(dest + TOP_RANGE, source + top, 16); + else { + for (j = 0; j < 16; j++) + dest[TOP_RANGE + j] = source[top + 15 - j]; + } + + if (bottom < 128) + memcpy(dest + BOTTOM_RANGE, source + bottom, 16); + else { + for (j = 0; j < 16; j++) + dest[BOTTOM_RANGE + j] = source[bottom + 15 - j]; + } + } +} + +/* +===================== +CL_ParseStatic +===================== +*/ +void CL_ParseStatic(int version) //johnfitz -- added a parameter +{ + entity_t *ent; + int i; + + i = cl.num_statics; + if (i >= MAX_STATIC_ENTITIES) + Host_Error("Too many static entities"); + + ent = &cl_static_entities[i]; + cl.num_statics++; + CL_ParseBaseline(ent, version); //johnfitz -- added second parameter + + // copy it to the current state + + ent->model = cl.model_precache[ent->baseline.modelindex]; + ent->lerpflags |= LERP_RESETANIM; //johnfitz -- lerping + ent->frame = ent->baseline.frame; + + ent->colormap = vid.colormap; + ent->skinnum = ent->baseline.skin; + ent->effects = ent->baseline.effects; + ent->alpha = ent->baseline.alpha; //johnfitz -- alpha + ent->scale = ent->baseline.scale; + VectorCopy(ent->baseline.origin, ent->origin); + VectorCopy(ent->baseline.angles, ent->angles); + R_AddEfrags(ent); +} + +/* +=================== +CL_ParseStaticSound +=================== +*/ +void CL_ParseStaticSound(int version) //johnfitz -- added argument +{ + vec3_t org; + int sound_num, vol, atten; + int i; + + for (i = 0; i < 3; i++) + org[i] = MSG_ReadCoord(cl.protocolflags); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (version == 2) + sound_num = MSG_ReadShort(); + else + sound_num = MSG_ReadByte(); + //johnfitz + + vol = MSG_ReadByte(); + atten = MSG_ReadByte(); + + S_StaticSound(cl.sound_precache[sound_num], org, vol, atten); +} + + +#if 0 /* for debugging. from fteqw. */ +static void CL_DumpPacket(void) { + int i, pos; + unsigned char *packet = net_message.data; + + Con_Printf("CL_DumpPacket, BEGIN:\n"); + pos = 0; + while (pos < net_message.cursize) { + Con_Printf("%5i ", pos); + for (i = 0; i < 16; i++) { + if (pos >= net_message.cursize) + Con_Printf(" X "); + else Con_Printf("%2x ", packet[pos]); + pos++; + } + pos -= 16; + for (i = 0; i < 16; i++) { + if (pos >= net_message.cursize) + Con_Printf("X"); + else if (packet[pos] == 0) + Con_Printf("."); + else Con_Printf("%c", packet[pos]); + pos++; + } + Con_Printf("\n"); + } + + Con_Printf("CL_DumpPacket, --- END ---\n"); +} +#endif /* CL_DumpPacket */ + +#define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x); + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage(void) { + int cmd; + int i; + const char *str; //johnfitz + int total, j, lastcmd; //johnfitz + + // + // if recording demos, copy the message out + // + if (cl_shownet.value == 1) + Con_Printf("%i ", net_message.cursize); + else if (cl_shownet.value == 2) + Con_Printf("------------------\n"); + + // cl.onground = false; // unless the server says otherwise + + // + // parse the message + // + MSG_BeginReading(); + + lastcmd = 0; + while (1) { + if (msg_badread) + Host_Error("CL_ParseServerMessage: Bad server message"); + + cmd = MSG_ReadByte(); + + if (cmd == -1) { + SHOWNET("END OF MESSAGE"); + return; // end of message + } + + // if the high bit of the command byte is set, it is a fast update + if (cmd & U_SIGNAL) //johnfitz -- was 128, changed for clarity + { + SHOWNET("fast update"); + CL_ParseUpdate(cmd & 127); + continue; + } + + if (cmd < (int) NUM_SVC_STRINGS) { + SHOWNET(svc_strings[cmd]); + } + + // other commands + switch (cmd) { + default: + // CL_DumpPacket (); + Host_Error("Illegible server message %d (previous was %s)", cmd, svc_strings[lastcmd]); + //johnfitz -- added svc_strings[lastcmd] + break; + + case svc_nop: + // Con_Printf ("svc_nop\n"); + break; + + case svc_time: + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = MSG_ReadFloat(); + break; + + case svc_clientdata: + CL_ParseClientdata(); + //johnfitz -- removed bits parameter, we will read this inside CL_ParseClientdata() + break; + + case svc_version: + i = MSG_ReadLong(); + //johnfitz -- support multiple protocols + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) + Host_Error("Server returned version %i, not %i or %i or %i", i, PROTOCOL_NETQUAKE, + PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); + cl.protocol = i; + //johnfitz + break; + + case svc_disconnect: + Host_EndGame("Server disconnected\n"); + + case svc_print: + Con_Printf("%s", MSG_ReadString()); + break; + + case svc_centerprint: + //johnfitz -- log centerprints to console + str = MSG_ReadString(); + SCR_CenterPrint(str); + Con_LogCenterPrint(str); + //johnfitz + break; + + case svc_stufftext: + Cbuf_AddText(MSG_ReadString()); + break; + + case svc_damage: + V_ParseDamage(); + break; + + case svc_serverinfo: + CL_ParseServerInfo(); + vid.recalc_refdef = true; // leave intermission full screen + break; + + case svc_setangle: + for (i = 0; i < 3; i++) + cl.viewangles[i] = MSG_ReadAngle(cl.protocolflags); + break; + + case svc_setview: + cl.viewentity = MSG_ReadShort(); + break; + + case svc_lightstyle: + i = MSG_ReadByte(); + if (i >= MAX_LIGHTSTYLES) + Sys_Error("svc_lightstyle > MAX_LIGHTSTYLES"); + q_strlcpy(cl_lightstyle[i].map, MSG_ReadString(), MAX_STYLESTRING); + cl_lightstyle[i].length = Q_strlen(cl_lightstyle[i].map); + //johnfitz -- save extra info + if (cl_lightstyle[i].length) { + total = 0; + cl_lightstyle[i].peak = 'a'; + for (j = 0; j < cl_lightstyle[i].length; j++) { + total += cl_lightstyle[i].map[j] - 'a'; + cl_lightstyle[i].peak = q_max(cl_lightstyle[i].peak, cl_lightstyle[i].map[j]); + } + cl_lightstyle[i].average = total / cl_lightstyle[i].length + 'a'; + } else + cl_lightstyle[i].average = cl_lightstyle[i].peak = 'm'; + //johnfitz + break; + + case svc_sound: + CL_ParseStartSoundPacket(); + break; + + case svc_stopsound: + i = MSG_ReadShort(); + S_StopSound(i >> 3, i & 7); + break; + + case svc_updatename: + Sbar_Changed(); + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatename > MAX_SCOREBOARD"); + q_strlcpy(cl.scores[i].name, MSG_ReadString(), MAX_SCOREBOARDNAME); + break; + + case svc_updatefrags: + Sbar_Changed(); + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD"); + cl.scores[i].frags = MSG_ReadShort(); + break; + + case svc_updatecolors: + Sbar_Changed(); + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatecolors > MAX_SCOREBOARD"); + cl.scores[i].colors = MSG_ReadByte(); + CL_NewTranslation(i); + break; + + case svc_particle: + R_ParseParticleEffect(); + break; + + case svc_spawnbaseline: + i = MSG_ReadShort(); + // must use CL_EntityNum() to force cl.num_entities up + CL_ParseBaseline(CL_EntityNum(i), 1); // johnfitz -- added second parameter + break; + + case svc_spawnstatic: + CL_ParseStatic(1); //johnfitz -- added parameter + break; + + case svc_temp_entity: + CL_ParseTEnt(); + break; + + case svc_setpause: + cl.paused = MSG_ReadByte(); + if (cl.paused) { + CDAudio_Pause(); + BGM_Pause(); + } else { + CDAudio_Resume(); + BGM_Resume(); + } + break; + + case svc_signonnum: + i = MSG_ReadByte(); + if (i <= cls.signon) + Host_Error("Received signon %i when at %i", i, cls.signon); + cls.signon = i; + //johnfitz -- if signonnum==2, signon packet has been fully parsed, so check for excessive static ents and efrags + if (i == 2) { + if (cl.num_statics > 128) + Con_DWarning("%i static entities exceeds standard limit of 128 (max = %d).\n", cl.num_statics, + MAX_STATIC_ENTITIES); + R_CheckEfrags(); + } + //johnfitz + CL_SignonReply(); + break; + + case svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; + + case svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; + + case svc_updatestat: + i = MSG_ReadByte(); + if (i < 0 || i >= MAX_CL_STATS) + Sys_Error("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadLong();; + break; + + case svc_spawnstaticsound: + CL_ParseStaticSound(1); //johnfitz -- added parameter + break; + + case svc_cdtrack: + cl.cdtrack = MSG_ReadByte(); + cl.looptrack = MSG_ReadByte(); + if ((cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1)) + BGM_PlayCDtrack((byte) cls.forcetrack, true); + else + BGM_PlayCDtrack((byte) cl.cdtrack, true); + break; + + case svc_intermission: + cl.intermission = 1; + cl.completed_time = cl.time; + vid.recalc_refdef = true; // go to full screen + V_RestoreAngles(); + break; + + case svc_finale: + cl.intermission = 2; + cl.completed_time = cl.time; + vid.recalc_refdef = true; // go to full screen + //johnfitz -- log centerprints to console + str = MSG_ReadString(); + SCR_CenterPrint(str); + Con_LogCenterPrint(str); + //johnfitz + V_RestoreAngles(); + break; + + case svc_cutscene: + cl.intermission = 3; + cl.completed_time = cl.time; + vid.recalc_refdef = true; // go to full screen + //johnfitz -- log centerprints to console + str = MSG_ReadString(); + SCR_CenterPrint(str); + Con_LogCenterPrint(str); + //johnfitz + V_RestoreAngles(); + break; + + case svc_sellscreen: + Cmd_ExecuteString("help", src_command); + break; + + //johnfitz -- new svc types + case svc_skybox: + Sky_LoadSkyBox(MSG_ReadString()); + break; + + case svc_bf: + Cmd_ExecuteString("bf", src_command); + break; + + case svc_fog: + Fog_ParseServerMessage(); + break; + + case svc_spawnbaseline2: //PROTOCOL_FITZQUAKE + i = MSG_ReadShort(); + // must use CL_EntityNum() to force cl.num_entities up + CL_ParseBaseline(CL_EntityNum(i), 2); + break; + + case svc_spawnstatic2: //PROTOCOL_FITZQUAKE + CL_ParseStatic(2); + break; + + case svc_spawnstaticsound2: //PROTOCOL_FITZQUAKE + CL_ParseStaticSound(2); + break; + //johnfitz + + //used by the 2021 rerelease + case svc_achievement: + str = MSG_ReadString(); + Con_DPrintf("Ignoring svc_achievement (%s)\n", str); + break; + case svc_localsound: + CL_ParseLocalSound(); + break; + } + + lastcmd = cmd; //johnfitz + } +} diff --git a/Quake/cl_tent.cpp b/Quake/cl_tent.cpp new file mode 100644 index 0000000..e1408b3 --- /dev/null +++ b/Quake/cl_tent.cpp @@ -0,0 +1,361 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_tent.c -- client side temporary entities + +#include "quakedef.hpp" + +int num_temp_entities; +entity_t cl_temp_entities[MAX_TEMP_ENTITIES]; +beam_t cl_beams[MAX_BEAMS]; + +sfx_t *cl_sfx_wizhit; +sfx_t *cl_sfx_knighthit; +sfx_t *cl_sfx_tink1; +sfx_t *cl_sfx_ric1; +sfx_t *cl_sfx_ric2; +sfx_t *cl_sfx_ric3; +sfx_t *cl_sfx_r_exp3; + +/* +================= +CL_ParseTEnt +================= +*/ +void CL_InitTEnts (void) +{ + cl_sfx_wizhit = S_PrecacheSound ("wizard/hit.wav"); + cl_sfx_knighthit = S_PrecacheSound ("hknight/hit.wav"); + cl_sfx_tink1 = S_PrecacheSound ("weapons/tink1.wav"); + cl_sfx_ric1 = S_PrecacheSound ("weapons/ric1.wav"); + cl_sfx_ric2 = S_PrecacheSound ("weapons/ric2.wav"); + cl_sfx_ric3 = S_PrecacheSound ("weapons/ric3.wav"); + cl_sfx_r_exp3 = S_PrecacheSound ("weapons/r_exp3.wav"); +} + +/* +================= +CL_ParseBeam +================= +*/ +void CL_ParseBeam (qmodel_t *m) +{ + int ent; + vec3_t start, end; + beam_t *b; + int i; + + ent = MSG_ReadShort (); + + start[0] = MSG_ReadCoord (cl.protocolflags); + start[1] = MSG_ReadCoord (cl.protocolflags); + start[2] = MSG_ReadCoord (cl.protocolflags); + + end[0] = MSG_ReadCoord (cl.protocolflags); + end[1] = MSG_ReadCoord (cl.protocolflags); + end[2] = MSG_ReadCoord (cl.protocolflags); + +// override any beam with the same entity + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->entity == ent) + { + b->entity = ent; + b->model = m; + b->endtime = cl.time + 0.2; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + return; + } + +// find a free beam + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + { + b->entity = ent; + b->model = m; + b->endtime = cl.time + 0.2; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + return; + } + } + + //johnfitz -- less spammy overflow message + if (!dev_overflows.beams || dev_overflows.beams + CONSOLE_RESPAM_TIME < realtime ) + { + Con_Printf ("Beam list overflow!\n"); + dev_overflows.beams = realtime; + } + //johnfitz +} + +/* +================= +CL_ParseTEnt +================= +*/ +void CL_ParseTEnt (void) +{ + int type; + vec3_t pos; + dlight_t *dl; + int rnd; + int colorStart, colorLength; + + type = MSG_ReadByte (); + switch (type) + { + case TE_WIZSPIKE: // spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 20, 30); + S_StartSound (-1, 0, cl_sfx_wizhit, pos, 1, 1); + break; + + case TE_KNIGHTSPIKE: // spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 226, 20); + S_StartSound (-1, 0, cl_sfx_knighthit, pos, 1, 1); + break; + + case TE_SPIKE: // spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 0, 10); + if ( rand() % 5 ) + S_StartSound (-1, 0, cl_sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound (-1, 0, cl_sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound (-1, 0, cl_sfx_ric2, pos, 1, 1); + else + S_StartSound (-1, 0, cl_sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKE: // super spike hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 0, 20); + + if ( rand() % 5 ) + S_StartSound (-1, 0, cl_sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound (-1, 0, cl_sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound (-1, 0, cl_sfx_ric2, pos, 1, 1); + else + S_StartSound (-1, 0, cl_sfx_ric3, pos, 1, 1); + } + break; + + case TE_GUNSHOT: // bullet hitting wall + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_RunParticleEffect (pos, vec3_origin, 0, 20); + break; + + case TE_EXPLOSION: // rocket explosion + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_ParticleExplosion (pos); + dl = CL_AllocDlight (0); + VectorCopy (pos, dl->origin); + dl->radius = 350; + dl->die = cl.time + 0.5; + dl->decay = 300; + S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + break; + + case TE_TAREXPLOSION: // tarbaby explosion + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_BlobExplosion (pos); + + S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + break; + + case TE_LIGHTNING1: // lightning bolts + CL_ParseBeam (Mod_ForName("progs/bolt.mdl", true)); + break; + + case TE_LIGHTNING2: // lightning bolts + CL_ParseBeam (Mod_ForName("progs/bolt2.mdl", true)); + break; + + case TE_LIGHTNING3: // lightning bolts + CL_ParseBeam (Mod_ForName("progs/bolt3.mdl", true)); + break; + +// PGM 01/21/97 + case TE_BEAM: // grappling hook beam + CL_ParseBeam (Mod_ForName("progs/beam.mdl", true)); + break; +// PGM 01/21/97 + + case TE_LAVASPLASH: + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_LavaSplash (pos); + break; + + case TE_TELEPORT: + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + R_TeleportSplash (pos); + break; + + case TE_EXPLOSION2: // color mapped explosion + pos[0] = MSG_ReadCoord (cl.protocolflags); + pos[1] = MSG_ReadCoord (cl.protocolflags); + pos[2] = MSG_ReadCoord (cl.protocolflags); + colorStart = MSG_ReadByte (); + colorLength = MSG_ReadByte (); + R_ParticleExplosion2 (pos, colorStart, colorLength); + dl = CL_AllocDlight (0); + VectorCopy (pos, dl->origin); + dl->radius = 350; + dl->die = cl.time + 0.5; + dl->decay = 300; + S_StartSound (-1, 0, cl_sfx_r_exp3, pos, 1, 1); + break; + + default: + Sys_Error ("CL_ParseTEnt: bad type"); + } +} + + +/* +================= +CL_NewTempEntity +================= +*/ +entity_t *CL_NewTempEntity (void) +{ + entity_t *ent; + + if (cl_numvisedicts == MAX_VISEDICTS) + return NULL; + if (num_temp_entities == MAX_TEMP_ENTITIES) + return NULL; + ent = &cl_temp_entities[num_temp_entities]; + memset (ent, 0, sizeof(*ent)); + num_temp_entities++; + cl_visedicts[cl_numvisedicts] = ent; + cl_numvisedicts++; + ent->scale = ENTSCALE_DEFAULT; + ent->colormap = vid.colormap; + return ent; +} + + +/* +================= +CL_UpdateTEnts +================= +*/ +void CL_UpdateTEnts (void) +{ + int i, j; //johnfitz -- use j instead of using i twice, so we don't corrupt memory + beam_t *b; + vec3_t dist, org; + float d; + entity_t *ent; + float yaw, pitch; + float forward; + + num_temp_entities = 0; + + srand ((int) (cl.time * 1000)); //johnfitz -- freeze beams when paused + +// update lightning + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + continue; + + // if coming from the player, update the start position + if (b->entity == cl.viewentity) + { + VectorCopy (cl_entities[cl.viewentity].origin, b->start); + } + + // calculate pitch and yaw + VectorSubtract (b->end, b->start, dist); + + if (dist[1] == 0 && dist[0] == 0) + { + yaw = 0; + if (dist[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (int) (atan2(dist[1], dist[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); + pitch = (int) (atan2(dist[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + // add new entities for the lightning + VectorCopy (b->start, org); + d = VectorNormalize(dist); + while (d > 0) + { + ent = CL_NewTempEntity (); + if (!ent) + return; + VectorCopy (org, ent->origin); + ent->model = b->model; + ent->angles[0] = pitch; + ent->angles[1] = yaw; + ent->angles[2] = rand()%360; + + //johnfitz -- use j instead of using i twice, so we don't corrupt memory + for (j=0 ; j<3 ; j++) + org[j] += dist[j]*30; + d -= 30; + } + } +} diff --git a/Quake/client.hpp b/Quake/client.hpp new file mode 100644 index 0000000..e00d0ad --- /dev/null +++ b/Quake/client.hpp @@ -0,0 +1,379 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _CLIENT_H_ +#define _CLIENT_H_ + +// client.h + +typedef struct +{ + int length; + char map[MAX_STYLESTRING]; + char average; //johnfitz + char peak; //johnfitz +} lightstyle_t; + +typedef struct +{ + char name[MAX_SCOREBOARDNAME]; + float entertime; + int frags; + int colors; // two 4 bit fields + byte translations[VID_GRADES*256]; +} scoreboard_t; + +typedef struct +{ + int destcolor[3]; + int percent; // 0-256 +} cshift_t; + +#define CSHIFT_CONTENTS 0 +#define CSHIFT_DAMAGE 1 +#define CSHIFT_BONUS 2 +#define CSHIFT_POWERUP 3 +#define NUM_CSHIFTS 4 + +#define NAME_LENGTH 64 + + +// +// client_state_t should hold all pieces of the client state +// + +#define SIGNONS 4 // signon messages to receive before connected + +#define MAX_DLIGHTS 64 //johnfitz -- was 32 +typedef struct +{ + vec3_t origin; + float radius; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + vec3_t color; //johnfitz -- lit support via lordhavoc +} dlight_t; + + +#define MAX_BEAMS 32 //johnfitz -- was 24 +typedef struct +{ + int entity; + struct qmodel_s *model; + float endtime; + vec3_t start, end; +} beam_t; + +#define MAX_MAPSTRING 2048 +#define MAX_DEMOS 8 +#define MAX_DEMONAME 16 + +typedef enum { +ca_dedicated, // a dedicated server with no ability to start a client +ca_disconnected, // full screen console with no connection +ca_connected // valid netcon, talking to a server +} cactive_t; + +// +// the client_static_t structure is persistant through an arbitrary number +// of server connections +// +typedef struct +{ + cactive_t state; + +// personalization data sent to server + char spawnparms[MAX_MAPSTRING]; // to restart a level + +// demo loop control + int demonum; // -1 = don't play demos + char demos[MAX_DEMOS][MAX_DEMONAME]; // when not playing + +// demo recording info must be here, because record is started before +// entering a map (and clearing client_state_t) + qboolean demorecording; + qboolean demoplayback; + +// did the user pause demo playback? (separate from cl.paused because we don't +// want a svc_setpause inside the demo to actually pause demo playback). + qboolean demopaused; + + qboolean timedemo; + int forcetrack; // -1 = use normal cd track + FILE *demofile; + int td_lastframe; // to meter out one message a frame + int td_startframe; // host_framecount at start + float td_starttime; // realtime at second frame of timedemo + +// connection information + int signon; // 0 to SIGNONS + struct qsocket_s *netcon; + sizebuf_t message; // writing buffer to send to server + +} client_static_t; + +extern client_static_t cls; + +// +// the client_state_t structure is wiped completely at every +// server signon +// +typedef struct +{ + int movemessages; // since connecting to this server + // throw out the first couple, so the player + // doesn't accidentally do something the + // first frame + usercmd_t cmd; // last command sent to the server + +// information for local display + int stats[MAX_CL_STATS]; // health, etc + int items; // inventory bit flags + float item_gettime[32]; // cl.time of aquiring item, for blinking + float faceanimtime; // use anim frame if cl.time < this + + cshift_t cshifts[NUM_CSHIFTS]; // color shifts for damage, powerups + cshift_t prev_cshifts[NUM_CSHIFTS]; // and content types + +// the client maintains its own idea of view angles, which are +// sent to the server each frame. The server sets punchangle when +// the view is temporarliy offset, and an angle reset commands at the start +// of each level and after teleporting. + vec3_t mviewangles[2]; // during demo playback viewangles is lerped + // between these + vec3_t viewangles; + + vec3_t mvelocity[2]; // update by server, used for lean+bob + // (0 is newest) + vec3_t velocity; // lerped between mvelocity[0] and [1] + + vec3_t punchangle; // temporary offset + +// pitch drifting vars + float idealpitch; + float pitchvel; + qboolean nodrift; + float driftmove; + double laststop; + + float viewheight; + float crouch; // local amount for smoothing stepups + + qboolean paused; // send over by server + qboolean onground; + qboolean inwater; + + int intermission; // don't change view angle, full screen, etc + int completed_time; // latched at intermission start + + double mtime[2]; // the timestamp of last two messages + double time; // clients view of time, should be between + // servertime and oldservertime to generate + // a lerp point for other data + double oldtime; // previous cl.time, time-oldtime is used + // to decay light values and smooth step ups + + + float last_received_message; // (realtime) for net trouble icon + +// +// information that is static for the entire time connected to a server +// + struct qmodel_s *model_precache[MAX_MODELS]; + struct sfx_s *sound_precache[MAX_SOUNDS]; + + char mapname[128]; + char levelname[128]; // for display on solo scoreboard //johnfitz -- was 40. + int viewentity; // cl_entitites[cl.viewentity] = player + int maxclients; + int gametype; + +// refresh related state + struct qmodel_s *worldmodel; // cl_entitites[0].model + struct efrag_s *free_efrags; + int num_efrags; + int num_entities; // held in cl_entities array + int num_statics; // held in cl_staticentities array + entity_t viewent; // the gun model + + int cdtrack, looptrack; // cd audio + +// frag scoreboard + scoreboard_t *scores; // [cl.maxclients] + + unsigned protocol; //johnfitz + unsigned protocolflags; +} client_state_t; + + +// +// cvars +// +extern cvar_t cl_name; +extern cvar_t cl_color; + +extern cvar_t cl_upspeed; +extern cvar_t cl_forwardspeed; +extern cvar_t cl_backspeed; +extern cvar_t cl_sidespeed; + +extern cvar_t cl_movespeedkey; + +extern cvar_t cl_yawspeed; +extern cvar_t cl_pitchspeed; + +extern cvar_t cl_anglespeedkey; + +extern cvar_t cl_alwaysrun; // QuakeSpasm + +extern cvar_t cl_autofire; + +extern cvar_t cl_shownet; +extern cvar_t cl_nolerp; + +extern cvar_t cfg_unbindall; + +extern cvar_t cl_pitchdriftspeed; +extern cvar_t lookspring; +extern cvar_t lookstrafe; +extern cvar_t sensitivity; + +extern cvar_t m_pitch; +extern cvar_t m_yaw; +extern cvar_t m_forward; +extern cvar_t m_side; + +extern cvar_t cl_startdemos; + + +#define MAX_TEMP_ENTITIES 256 //johnfitz -- was 64 +#define MAX_STATIC_ENTITIES 4096 //ericw -- was 512 //johnfitz -- was 128 +#define MAX_VISEDICTS 4096 // larger, now we support BSP2 + +extern client_state_t cl; + +// FIXME, allocate dynamically +extern entity_t cl_static_entities[MAX_STATIC_ENTITIES]; +extern lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; +extern dlight_t cl_dlights[MAX_DLIGHTS]; +extern entity_t cl_temp_entities[MAX_TEMP_ENTITIES]; +extern beam_t cl_beams[MAX_BEAMS]; +extern entity_t *cl_visedicts[MAX_VISEDICTS]; +extern int cl_numvisedicts; + +extern entity_t *cl_entities; //johnfitz -- was a static array, now on hunk +extern int cl_max_edicts; //johnfitz -- only changes when new map loads + +//============================================================================= + +// +// cl_main +// +dlight_t *CL_AllocDlight (int key); +void CL_DecayLights (void); + +void CL_Init (void); + +void CL_EstablishConnection (const char *host); +void CL_Signon1 (void); +void CL_Signon2 (void); +void CL_Signon3 (void); +void CL_Signon4 (void); + +void CL_Disconnect (void); +void CL_Disconnect_f (void); +void CL_NextDemo (void); + +// +// cl_input +// +typedef struct +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +void CL_InitInput (void); +void CL_SendCmd (void); +void CL_SendMove (const usercmd_t *cmd); +int CL_ReadFromServer (void); +void CL_BaseMove (usercmd_t *cmd); + +void CL_ParseTEnt (void); +void CL_UpdateTEnts (void); + +void CL_ClearState (void); + +// +// cl_demo.c +// +void CL_StopPlayback (void); +int CL_GetMessage (void); +void CL_ClearSignons (void); + +void CL_Stop_f (void); +void CL_Record_f (void); +void CL_PlayDemo_f (void); +void CL_TimeDemo_f (void); + +// +// cl_parse.c +// +void CL_ParseServerMessage (void); +void CL_NewTranslation (int slot); + +// +// view +// +void V_StartPitchDrift (void); +void V_StopPitchDrift (void); + +void V_RenderView (void); +//void V_UpdatePalette (void); //johnfitz +void V_Register (void); +void V_ParseDamage (void); +void V_SetContentsColor (int contents); + +// +// cl_tent +// +void CL_InitTEnts (void); +void CL_SignonReply (void); + +// +// chase +// +extern cvar_t chase_active; + +void Chase_Init (void); +void TraceLine (vec3_t start, vec3_t end, vec3_t impact); +void Chase_UpdateForClient (void); //johnfitz +void Chase_UpdateForDrawing (void); //johnfitz + +#endif /* _CLIENT_H_ */ + diff --git a/Quake/cmd.cpp b/Quake/cmd.cpp new file mode 100644 index 0000000..f3d2be0 --- /dev/null +++ b/Quake/cmd.cpp @@ -0,0 +1,869 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cmd.c -- Quake script command processing module + +#include "quakedef.hpp" + +void Cmd_ForwardToServer (void); + +#define MAX_ALIAS_NAME 32 + +#define CMDLINE_LENGTH 256 //johnfitz -- mirrored in common.c + +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; +} cmdalias_t; + +cmdalias_t *cmd_alias; + +qboolean cmd_wait; + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" +============ +*/ +void Cmd_Wait_f (void) +{ + cmd_wait = true; +} + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +sizebuf_t cmd_text; + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ + SZ_Alloc (&cmd_text, 1<<18); // space for commands and script files. spike -- was 8192, but modern configs can be _HUGE_, at least if they contain lots of comments/docs for things. +} + + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddText (const char *text) +{ + int l; + + l = Q_strlen (text); + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Con_Printf ("Cbuf_AddText: overflow\n"); + return; + } + + SZ_Write (&cmd_text, text, Q_strlen (text)); +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +FIXME: actually change the command buffer to do less copying +============ +*/ +void Cbuf_InsertText (const char *text) +{ + char *temp; + int templen; + +// copy off any commands still remaining in the exec buffer + templen = cmd_text.cursize; + if (templen) + { + temp = (char *) Z_Malloc (templen); + Q_memcpy (temp, cmd_text.data, templen); + SZ_Clear (&cmd_text); + } + else + temp = NULL; // shut up compiler + +// add the entire text of the file + Cbuf_AddText (text); + SZ_Write (&cmd_text, "\n", 1); +// add the copied off data + if (templen) + { + SZ_Write (&cmd_text, temp, templen); + Z_Free (temp); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[1024]; + int quotes; + + while (cmd_text.cursize) + { +// find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = 0; + for (i=0 ; i< cmd_text.cursize ; i++) + { + if (text[i] == '"') + quotes++; + if ( !(quotes&1) && text[i] == ';') + break; // don't break if inside a quoted string + if (text[i] == '\n') + break; + } + + if (i > (int)sizeof(line) - 1) + { + memcpy (line, text, sizeof(line) - 1); + line[sizeof(line) - 1] = 0; + } + else + { + 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, alias) 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, src_command); + + if (cmd_wait) + { // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait = false; + break; + } + } +} + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + +/* +=============== +Cmd_StuffCmds_f -- johnfitz -- rewritten to read the "cmdline" cvar, for use with dynamic mod loading + +Adds command line parameters as script statements +Commands lead with a +, and continue until a - or another + +quake +prog jctest.qp +cmd amlev1 +quake -nosound +cmd amlev1 +=============== +*/ +void Cmd_StuffCmds_f (void) +{ + extern cvar_t cmdline; + char cmds[CMDLINE_LENGTH]; + int i, j, plus; + + plus = false; // On Unix, argv[0] is command name + + for (i = 0, j = 0; cmdline.string[i]; i++) + { + if (cmdline.string[i] == '+') + { + plus = true; + if (j > 0) + { + cmds[j-1] = ';'; + cmds[j++] = ' '; + } + } + else if (cmdline.string[i] == '-' && + (i==0 || cmdline.string[i-1] == ' ')) //johnfitz -- allow hypenated map names with +map + plus = false; + else if (plus) + cmds[j++] = cmdline.string[i]; + } + cmds[j] = 0; + + Cbuf_InsertText (cmds); +} + +/* id1/pak0.pak from 2021 re-release doesn't have a default.cfg + * embedding Quakespasm's customized default.cfg for that... */ +#include "default_cfg.hpp" + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f (void) +{ + const char *f; + int mark; + + if (Cmd_Argc () != 2) + { + Con_Printf ("exec : execute a script file\n"); + return; + } + + mark = Hunk_LowMark (); + f = (const char *)COM_LoadHunkFile (Cmd_Argv(1), NULL); + if (!f && !strcmp(Cmd_Argv(1), "default.cfg")) { + f = default_cfg; /* see above.. */ + } + if (!f) + { + Con_Printf ("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + Con_Printf ("execing %s\n",Cmd_Argv(1)); + + Cbuf_InsertText (f); + if (f != default_cfg) { + Hunk_FreeToLowMark (mark); + } +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ + int i; + + for (i=1 ; inext, i++) + Con_SafePrintf (" %s: %s", a->name, a->value); + if (i) + Con_SafePrintf ("%i alias command(s)\n", i); + else + Con_SafePrintf ("no alias commands found\n"); + break; + case 2: //output current alias string + for (a = cmd_alias ; a ; a=a->next) + if (!strcmp(Cmd_Argv(1), a->name)) + Con_Printf (" %s: %s", a->name, a->value); + break; + default: //set alias string + s = Cmd_Argv(1); + if (strlen(s) >= MAX_ALIAS_NAME) + { + Con_Printf ("Alias name is too long\n"); + return; + } + + // if the alias already exists, reuse it + for (a = cmd_alias ; a ; a=a->next) + { + if (!strcmp(s, a->name)) + { + Z_Free (a->value); + break; + } + } + + if (!a) + { + a = (cmdalias_t *) Z_Malloc (sizeof(cmdalias_t)); + a->next = cmd_alias; + cmd_alias = a; + } + strcpy (a->name, s); + + // copy the rest of the command line + cmd[0] = 0; // start out with a null string + c = Cmd_Argc(); + for (i = 2; i < c; i++) + { + q_strlcat (cmd, Cmd_Argv(i), sizeof(cmd)); + if (i != c - 1) + q_strlcat (cmd, " ", sizeof(cmd)); + } + if (q_strlcat(cmd, "\n", sizeof(cmd)) >= sizeof(cmd)) + { + Con_Printf("alias value too long!\n"); + cmd[0] = '\n'; // nullify the string + cmd[1] = 0; + } + + a->value = Z_Strdup (cmd); + break; + } +} + +/* +=============== +Cmd_Unalias_f -- johnfitz +=============== +*/ +void Cmd_Unalias_f (void) +{ + cmdalias_t *a, *prev; + + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf("unalias : delete alias\n"); + break; + case 2: + prev = NULL; + for (a = cmd_alias; a; a = a->next) + { + if (!strcmp(Cmd_Argv(1), a->name)) + { + if (prev) + prev->next = a->next; + else + cmd_alias = a->next; + + Z_Free (a->value); + Z_Free (a); + return; + } + prev = a; + } + Con_Printf ("No alias named %s\n", Cmd_Argv(1)); + break; + } +} + +/* +=============== +Cmd_Unaliasall_f -- johnfitz +=============== +*/ +void Cmd_Unaliasall_f (void) +{ + cmdalias_t *blah; + + while (cmd_alias) + { + blah = cmd_alias->next; + Z_Free(cmd_alias->value); + Z_Free(cmd_alias); + cmd_alias = blah; + } +} + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + xcommand_t function; +} cmd_function_t; + + +#define MAX_ARGS 80 + +static int cmd_argc; +static char *cmd_argv[MAX_ARGS]; +static char cmd_null_string[] = ""; +static const char *cmd_args = NULL; + +cmd_source_t cmd_source; + +//johnfitz -- better tab completion +//static cmd_function_t *cmd_functions; // possible commands to execute +cmd_function_t *cmd_functions; // possible commands to execute +//johnfitz + +/* +============ +Cmd_List_f -- johnfitz +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t *cmd; + const char *partial; + int len, count; + + if (Cmd_Argc() > 1) + { + partial = Cmd_Argv (1); + len = Q_strlen(partial); + } + else + { + partial = NULL; + len = 0; + } + + count=0; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (partial && Q_strncmp (partial,cmd->name, len)) + { + continue; + } + Con_SafePrintf (" %s\n", cmd->name); + count++; + } + + Con_SafePrintf ("%i commands", count); + if (partial) + { + Con_SafePrintf (" beginning with \"%s\"", partial); + } + Con_SafePrintf ("\n"); +} + +static char *Cmd_TintSubstring(const char *in, const char *substr, char *out, size_t outsize) +{ + int l; + char *m; + q_strlcpy(out, in, outsize); + while ((m = q_strcasestr(out, substr))) + { + l = strlen(substr); + while (l-->0) + if (*m >= ' ' && *m < 127) + *m++ |= 0x80; + } + return out; +} + +/* +============ +Cmd_Apropos_f + +scans through each command and cvar names+descriptions for the given substring +we don't support descriptions, so this isn't really all that useful, but even without the sake of consistency it still combines cvars+commands under a single command. +============ +*/ +void Cmd_Apropos_f(void) +{ + char tmpbuf[256]; + int hits = 0; + cmd_function_t *cmd; + cvar_t *var; + const char *substr = Cmd_Argv (1); + if (!*substr) + { + Con_SafePrintf ("%s : search through commands and cvars for the given substring\n", Cmd_Argv(0)); + return; + } + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (q_strcasestr(cmd->name, substr)) + { + hits++; + Con_SafePrintf ("%s\n", Cmd_TintSubstring(cmd->name, substr, tmpbuf, sizeof(tmpbuf))); + } + } + + // for (var=Cvar_FindVarAfter("", 0) ; var ; var=var->next) + // { + // if (q_strcasestr(var->name, substr)) + // { + // hits++; + // Con_SafePrintf ("%s (current value: \"%s\")\n", Cmd_TintSubstring(var->name, substr, tmpbuf, sizeof(tmpbuf)), var->string); + // } + // } // TODO: + if (!hits) + Con_SafePrintf ("no cvars nor commands contain that substring\n"); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) +{ + Cmd_AddCommand ("cmdlist", Cmd_List_f); //johnfitz + Cmd_AddCommand ("unalias", Cmd_Unalias_f); //johnfitz + Cmd_AddCommand ("unaliasall", Cmd_Unaliasall_f); //johnfitz + + Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f); + Cmd_AddCommand ("exec",Cmd_Exec_f); + Cmd_AddCommand ("echo",Cmd_Echo_f); + Cmd_AddCommand ("alias",Cmd_Alias_f); + Cmd_AddCommand ("cmd", Cmd_ForwardToServer); + Cmd_AddCommand ("wait", Cmd_Wait_f); + + Cmd_AddCommand ("apropos", Cmd_Apropos_f); + Cmd_AddCommand ("find", Cmd_Apropos_f); +} + +/* +============ +Cmd_Argc +============ +*/ +int Cmd_Argc (void) +{ + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +const char *Cmd_Argv (int arg) +{ + if (arg < 0 || arg >= cmd_argc) + return cmd_null_string; + return cmd_argv[arg]; +} + +/* +============ +Cmd_Args +============ +*/ +const char *Cmd_Args (void) +{ + return cmd_args; +} + + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +============ +*/ +void Cmd_TokenizeString (const char *text) +{ + int i; + +// clear the args from the last string + for (i=0 ; inext) + { + if (!Q_strcmp (cmd_name, cmd->name)) + { + Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); + return; + } + } + + cmd = (cmd_function_t *) Hunk_Alloc (sizeof(cmd_function_t)); + cmd->name = cmd_name; + cmd->function = function; + + //johnfitz -- insert each entry in alphabetical order + if (cmd_functions == NULL || strcmp(cmd->name, cmd_functions->name) < 0) //insert at front + { + cmd->next = cmd_functions; + cmd_functions = cmd; + } + else //insert later + { + prev = cmd_functions; + cursor = cmd_functions->next; + while ((cursor != NULL) && (strcmp(cmd->name, cursor->name) > 0)) + { + prev = cursor; + cursor = cursor->next; + } + cmd->next = prev->next; + prev->next = cmd; + } + //johnfitz +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists (const char *cmd_name) +{ + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!Q_strcmp (cmd_name,cmd->name)) + return true; + } + + return false; +} + + + +/* +============ +Cmd_CompleteCommand +============ +*/ +const char *Cmd_CompleteCommand (const char *partial) +{ + cmd_function_t *cmd; + int len; + + len = Q_strlen(partial); + + if (!len) + return NULL; + +// check functions + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + if (!Q_strncmp (partial,cmd->name, len)) + return cmd->name; + + return NULL; +} + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +FIXME: lookupnoadd the token to speed search? +============ +*/ +void Cmd_ExecuteString (const char *text, cmd_source_t src) +{ + cmd_function_t *cmd; + cmdalias_t *a; + + cmd_source = src; + Cmd_TokenizeString (text); + +// execute the command line + if (!Cmd_Argc()) + return; // no tokens + +// check functions + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + { + if (!q_strcasecmp (cmd_argv[0],cmd->name)) + { + cmd->function (); + return; + } + } + +// check alias + for (a=cmd_alias ; a ; a=a->next) + { + if (!q_strcasecmp (cmd_argv[0], a->name)) + { + Cbuf_InsertText (a->value); + return; + } + } + +// check cvars + if (!Cvar_Command ()) + Con_Printf ("Unknown command \"%s\"\n", Cmd_Argv(0)); +} + + +/* +=================== +Cmd_ForwardToServer + +Sends the entire command line over to the server +=================== +*/ +void Cmd_ForwardToServer (void) +{ + if (cls.state != ca_connected) + { + Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0)); + return; + } + + if (cls.demoplayback) + return; // not really connected + + MSG_WriteByte (&cls.message, clc_stringcmd); + if (q_strcasecmp(Cmd_Argv(0), "cmd") != 0) + { + SZ_Print (&cls.message, Cmd_Argv(0)); + SZ_Print (&cls.message, " "); + } + if (Cmd_Argc() > 1) + SZ_Print (&cls.message, Cmd_Args()); + else + SZ_Print (&cls.message, "\n"); +} + + +/* +================ +Cmd_CheckParm + +Returns the position (1 to argc-1) in the command's argument list +where the given parameter apears, or 0 if not present +================ +*/ + +int Cmd_CheckParm (const char *parm) +{ + int i; + + if (!parm) + Sys_Error ("Cmd_CheckParm: null input\n"); + + for (i = 1; i < Cmd_Argc (); i++) + if ( !q_strcasecmp (parm, Cmd_Argv (i)) ) + return i; + + return 0; +} + diff --git a/Quake/cmd.hpp b/Quake/cmd.hpp new file mode 100644 index 0000000..9f688e2 --- /dev/null +++ b/Quake/cmd.hpp @@ -0,0 +1,127 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_CMD_H +#define _QUAKE_CMD_H + +// cmd.h -- Command buffer 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 remote +servers can also send across commands and entire text files can be execed. + +The + command line options are also added to the command buffer. + +The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute (); + +*/ + +void Cbuf_Init (void); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText (const char *text); +// as new commands are generated from the console or keybindings, +// the text is added to the end of the command buffer. + +void Cbuf_InsertText (const char *text); +// when a command wants to issue other commands immediately, the text is +// inserted at the beginning of the buffer, before any remaining unexecuted +// commands. + +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! + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +Commands can come from three sources, but the handler functions may choose +to dissallow the action or forward it to a remote server if the source is +not apropriate. + +*/ + +typedef void (*xcommand_t) (void); + +typedef enum +{ + src_client, // came in over a net connection as a clc_stringcmd + // host_client will be valid during this state. + src_command // from the command buffer +} cmd_source_t; + +extern cmd_source_t cmd_source; + +void Cmd_Init (void); + +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 + +qboolean Cmd_Exists (const char *cmd_name); +// used by the cvar code to check for cvar / command name overlap + +const char *Cmd_CompleteCommand (const char *partial); +// attempts to match a partial command for automatic command line completion +// returns NULL if nothing fits + +int Cmd_Argc (void); +const char *Cmd_Argv (int arg); +const char *Cmd_Args (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. + +int Cmd_CheckParm (const char *parm); +// Returns the position (1 to argc-1) in the command's argument list +// where the given parameter apears, or 0 if not present + +void Cmd_TokenizeString (const char *text); +// 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, cmd_source_t src); +// Parses a single line of text into arguments and tries to execute it. +// The text can come from the command buffer, a remote client, or stdin. + +void Cmd_ForwardToServer (void); +// adds the current command line as a clc_stringcmd to the client message. +// things like godmode, noclip, etc, are commands directed to the server, +// so when they are typed in at the console, they will need to be forwarded. + +void Cmd_Print (const char *text); +// used by command functions to send output to either the graphics console or +// passed as a print message to the client + +#endif /* _QUAKE_CMD_H */ + diff --git a/Quake/common.cpp b/Quake/common.cpp new file mode 100644 index 0000000..029c7f1 --- /dev/null +++ b/Quake/common.cpp @@ -0,0 +1,3022 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// common.c -- misc functions used in client and server + +#include "quakedef.hpp" +#include "q_ctype.hpp" +#include + +#include "miniz.hpp" + +static char *largv[MAX_NUM_ARGVS + 1]; +static char argvdummy[] = " "; + +int safemode; + +cvar_t registered = {"registered","1",CVAR_ROM}; /* set to correct value in COM_CheckRegistered() */ +cvar_t cmdline = {"cmdline","",CVAR_ROM/*|CVAR_SERVERINFO*/}; /* sending cmdline upon CCREQ_RULE_INFO is evil */ + +static qboolean com_modified; // set true if using non-id files + +qboolean fitzmode; + +static void COM_Path_f (void); + +// if a packfile directory differs from this, it is assumed to be hacked +#define PAK0_COUNT 339 /* id1/pak0.pak - v1.0x */ +#define PAK0_CRC_V100 13900 /* id1/pak0.pak - v1.00 */ +#define PAK0_CRC_V101 62751 /* id1/pak0.pak - v1.01 */ +#define PAK0_CRC_V106 32981 /* id1/pak0.pak - v1.06 */ +#define PAK0_CRC (PAK0_CRC_V106) +#define PAK0_COUNT_V091 308 /* id1/pak0.pak - v0.91/0.92, not supported */ +#define PAK0_CRC_V091 28804 /* id1/pak0.pak - v0.91/0.92, not supported */ + +char com_token[1024]; +int com_argc; +char **com_argv; + +#define CMDLINE_LENGTH 256 /* johnfitz -- mirrored in cmd.c */ +char com_cmdline[CMDLINE_LENGTH]; + +qboolean standard_quake = true, rogue, hipnotic; + +// this graphic needs to be in the pak file to use registered features +static unsigned short pop[] = +{ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x6600,0x0000,0x0000,0x0000,0x6600,0x0000, + 0x0000,0x0066,0x0000,0x0000,0x0000,0x0000,0x0067,0x0000, + 0x0000,0x6665,0x0000,0x0000,0x0000,0x0000,0x0065,0x6600, + 0x0063,0x6561,0x0000,0x0000,0x0000,0x0000,0x0061,0x6563, + 0x0064,0x6561,0x0000,0x0000,0x0000,0x0000,0x0061,0x6564, + 0x0064,0x6564,0x0000,0x6469,0x6969,0x6400,0x0064,0x6564, + 0x0063,0x6568,0x6200,0x0064,0x6864,0x0000,0x6268,0x6563, + 0x0000,0x6567,0x6963,0x0064,0x6764,0x0063,0x6967,0x6500, + 0x0000,0x6266,0x6769,0x6a68,0x6768,0x6a69,0x6766,0x6200, + 0x0000,0x0062,0x6566,0x6666,0x6666,0x6666,0x6562,0x0000, + 0x0000,0x0000,0x0062,0x6364,0x6664,0x6362,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0062,0x6662,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0061,0x6661,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x6500,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x6400,0x0000,0x0000,0x0000 +}; + +/* + +All of Quake's data access is through a hierchal file system, but the contents +of the file system can be transparently merged from several sources. + +The "base directory" is the path to the directory holding the quake.exe and all +game directories. The sys_* files pass this to host_init in quakeparms_t->basedir. +This can be overridden with the "-basedir" command line parm to allow code +debugging in a different directory. The base directory is only used during +filesystem initialization. + +The "game directory" is the first tree on the search path and directory that all +generated files (savegames, screenshots, demos, config files) will be saved to. +This can be overridden with the "-game" command line parameter. The game +directory can never be changed while quake is executing. This is a precacution +against having a malicious server instruct clients to write files over areas they +shouldn't. + +The "cache directory" is only used during development to save network bandwidth, +especially over ISDN / T1 lines. If there is a cache directory specified, when +a file is found by the normal search path, it will be mirrored into the cache +directory, then opened there. + +FIXME: +The file "parms.txt" will be read out of the game directory and appended to the +current command line arguments to allow different games to initialize startup +parms differently. This could be used to add a "-sspeed 22050" for the high +quality sound edition. Because they are added at the end, they will not +override an explicit setting on the original command line. + +*/ + +//============================================================================ + + +// ClearLink is used for new headnodes +void ClearLink (link_t *l) +{ + l->prev = l->next = l; +} + +void RemoveLink (link_t *l) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +void InsertLinkBefore (link_t *l, link_t *before) +{ + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +void InsertLinkAfter (link_t *l, link_t *after) +{ + l->next = after->next; + l->prev = after; + l->prev->next = l; + l->next->prev = l; +} + +/* +============================================================================ + + DYNAMIC VECTORS + +============================================================================ +*/ + +void Vec_Grow (void **pvec, size_t element_size, size_t count) +{ + vec_header_t header; + if (*pvec) + header = VEC_HEADER(*pvec); + else + header.size = header.capacity = 0; + + if (header.size + count > header.capacity) + { + void *new_buffer; + size_t total_size; + + header.capacity = header.size + count; + header.capacity += header.capacity >> 1; + if (header.capacity < 16) + header.capacity = 16; + total_size = sizeof(vec_header_t) + header.capacity * element_size; + + if (*pvec) + new_buffer = realloc (((vec_header_t*)*pvec) - 1, total_size); + else + new_buffer = malloc (total_size); + if (!new_buffer) + Sys_Error ("Vec_Grow: failed to allocate %lu bytes\n", (unsigned long)total_size); + + *pvec = 1 + (vec_header_t*)new_buffer; + VEC_HEADER(*pvec) = header; + } +} + +void Vec_Append (void **pvec, size_t element_size, const void *data, size_t count) +{ + if (!count) + return; + Vec_Grow (pvec, element_size, count); + memcpy ((byte *)*pvec + VEC_HEADER(*pvec).size, data, count * element_size); + VEC_HEADER(*pvec).size += count; +} + +void Vec_Clear (void **pvec) +{ + if (*pvec) + VEC_HEADER(*pvec).size = 0; +} + +void Vec_Free (void **pvec) +{ + if (*pvec) + { + free(&VEC_HEADER(*pvec)); + *pvec = NULL; + } +} + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int q_strcasecmp(const char * s1, const char * s2) +{ + const char * p1 = s1; + const char * p2 = s2; + char c1, c2; + + if (p1 == p2) + return 0; + + do + { + c1 = q_tolower (*p1++); + c2 = q_tolower (*p2++); + if (c1 == '\0') + break; + } while (c1 == c2); + + return (int)(c1 - c2); +} + +int q_strncasecmp(const char *s1, const char *s2, size_t n) +{ + const char * p1 = s1; + const char * p2 = s2; + char c1, c2; + + if (p1 == p2 || n == 0) + return 0; + + do + { + c1 = q_tolower (*p1++); + c2 = q_tolower (*p2++); + if (c1 == '\0' || c1 != c2) + break; + } while (--n > 0); + + return (int)(c1 - c2); +} + +char *q_strcasestr(const char *haystack, const char *needle) +{ + const size_t len = strlen(needle); + + while (*haystack) + { + if (!q_strncasecmp(haystack, needle, len)) + return (char *)haystack; + + ++haystack; + } + + return NULL; +} + +char *q_strlwr (char *str) +{ + char *c; + c = str; + while (*c) + { + *c = q_tolower(*c); + c++; + } + return str; +} + +char *q_strupr (char *str) +{ + char *c; + c = str; + while (*c) + { + *c = q_toupper(*c); + c++; + } + return str; +} + +/* platform dependant (v)snprintf function names: */ +#if defined(_WIN32) +#define snprintf_func _snprintf +#define vsnprintf_func _vsnprintf +#else +#define snprintf_func snprintf +#define vsnprintf_func vsnprintf +#endif + +int q_vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int ret; + + ret = vsnprintf_func (str, size, format, args); + + if (ret < 0) + ret = (int)size; + if (size == 0) /* no buffer */ + return ret; + if ((size_t)ret >= size) + str[size - 1] = '\0'; + + return ret; +} + +int q_snprintf (char *str, size_t size, const char *format, ...) +{ + int ret; + va_list argptr; + + va_start (argptr, format); + ret = q_vsnprintf (str, size, format, argptr); + va_end (argptr); + + return ret; +} + +void Q_memset (void *dest, int fill, size_t count) +{ + size_t i; + + if ( (((uintptr_t)dest | count) & 3) == 0) + { + count >>= 2; + fill = fill | (fill<<8) | (fill<<16) | (fill<<24); + for (i = 0; i < count; i++) + ((int *)dest)[i] = fill; + } + else + for (i = 0; i < count; i++) + ((byte *)dest)[i] = fill; +} + +void Q_memcpy (void *dest, const void *src, size_t count) +{ + size_t i; + + if (( ( (uintptr_t)dest | (uintptr_t)src | count) & 3) == 0) + { + count >>= 2; + for (i = 0; i < count; i++) + ((int *)dest)[i] = ((int *)src)[i]; + } + else + for (i = 0; i < count; i++) + ((byte *)dest)[i] = ((byte *)src)[i]; +} + +int Q_memcmp (const void *m1, const void *m2, size_t count) +{ + while(count) + { + count--; + if (((byte *)m1)[count] != ((byte *)m2)[count]) + return -1; + } + return 0; +} + +void Q_strcpy (char *dest, const char *src) +{ + while (*src) + { + *dest++ = *src++; + } + *dest++ = 0; +} + +void Q_strncpy (char *dest, const char *src, int count) +{ + while (*src && count--) + { + *dest++ = *src++; + } + if (count) + *dest++ = 0; +} + +int Q_strlen (const char *str) +{ + int count; + + count = 0; + while (str[count]) + count++; + + return count; +} + +char *Q_strrchr(const char *s, char c) +{ + int len = Q_strlen(s); + s += len; + while (len--) + { + if (*--s == c) + return (char *)s; + } + return NULL; +} + +void Q_strcat (char *dest, const char *src) +{ + dest += Q_strlen(dest); + Q_strcpy (dest, src); +} + +int Q_strcmp (const char *s1, const char *s2) +{ + while (1) + { + if (*s1 != *s2) + return -1; // strings not equal + if (!*s1) + return 0; // strings are equal + s1++; + s2++; + } + + return -1; +} + +int Q_strncmp (const char *s1, const char *s2, int count) +{ + while (1) + { + if (!count--) + return 0; + if (*s1 != *s2) + return -1; // strings not equal + if (!*s1) + return 0; // strings are equal + s1++; + s2++; + } + + return -1; +} + +int Q_atoi (const char *str) +{ + int val; + int sign; + int c; + + while (q_isspace (*str)) + ++str; + + if (*str == '-') + { + sign = -1; + str++; + } + else + sign = 1; + + val = 0; + +// +// check for hex +// + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + { + str += 2; + while (1) + { + c = *str++; + if (c >= '0' && c <= '9') + val = (val<<4) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val<<4) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val<<4) + c - 'A' + 10; + else + return val*sign; + } + } + +// +// check for character +// + if (str[0] == '\'') + { + return sign * str[1]; + } + +// +// assume decimal +// + while (1) + { + c = *str++; + if (c <'0' || c > '9') + return val*sign; + val = val*10 + c - '0'; + } + + return 0; +} + + +float Q_atof (const char *str) +{ + double val; + int sign; + int c; + int decimal, total; + + while (q_isspace (*str)) + ++str; + + if (*str == '-') + { + sign = -1; + str++; + } + else + sign = 1; + + val = 0; + +// +// check for hex +// + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X') ) + { + str += 2; + while (1) + { + c = *str++; + if (c >= '0' && c <= '9') + val = (val*16) + c - '0'; + else if (c >= 'a' && c <= 'f') + val = (val*16) + c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + val = (val*16) + c - 'A' + 10; + else + return val*sign; + } + } + +// +// check for character +// + if (str[0] == '\'') + { + return sign * str[1]; + } + +// +// assume decimal +// + decimal = -1; + total = 0; + while (1) + { + c = *str++; + if (c == '.') + { + decimal = total; + continue; + } + if (c <'0' || c > '9') + break; + val = val*10 + c - '0'; + total++; + } + + if (decimal == -1) + return val*sign; + while (total > decimal) + { + val /= 10; + total--; + } + + return val*sign; +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean host_bigendian; + +short (*BigShort) (short l); +short (*LittleShort) (short l); +int (*BigLong) (int l); +int (*LittleLong) (int l); +float (*BigFloat) (float l); +float (*LittleFloat) (float l); + +short ShortSwap (short l) +{ + byte b1, b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1, b2, b3, b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +// +// writing functions +// + +void MSG_WriteChar (sizebuf_t *sb, int c) +{ + byte *buf; + +#ifdef PARANOID + if (c < -128 || c > 127) + Sys_Error ("MSG_WriteChar: range error"); +#endif + + buf = (byte *) SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteByte (sizebuf_t *sb, int c) +{ + byte *buf; + +#ifdef PARANOID + if (c < 0 || c > 255) + Sys_Error ("MSG_WriteByte: range error"); +#endif + + buf = (byte *) SZ_GetSpace (sb, 1); + buf[0] = c; +} + +void MSG_WriteShort (sizebuf_t *sb, int c) +{ + byte *buf; + +#ifdef PARANOID + if (c < ((short)0x8000) || c > (short)0x7fff) + Sys_Error ("MSG_WriteShort: range error"); +#endif + + buf = (byte *) SZ_GetSpace (sb, 2); + buf[0] = c&0xff; + buf[1] = c>>8; +} + +void MSG_WriteLong (sizebuf_t *sb, int c) +{ + byte *buf; + + buf = (byte *) SZ_GetSpace (sb, 4); + buf[0] = c&0xff; + buf[1] = (c>>8)&0xff; + buf[2] = (c>>16)&0xff; + buf[3] = c>>24; +} + +void MSG_WriteFloat (sizebuf_t *sb, float f) +{ + union + { + float f; + int l; + } dat; + + dat.f = f; + dat.l = LittleLong (dat.l); + + SZ_Write (sb, &dat.l, 4); +} + +void MSG_WriteString (sizebuf_t *sb, const char *s) +{ + if (!s) + SZ_Write (sb, "", 1); + else + SZ_Write (sb, s, Q_strlen(s)+1); +} + +//johnfitz -- original behavior, 13.3 fixed point coords, max range +-4096 +void MSG_WriteCoord16 (sizebuf_t *sb, float f) +{ + MSG_WriteShort (sb, Q_rint(f*8)); +} + +//johnfitz -- 16.8 fixed point coords, max range +-32768 +void MSG_WriteCoord24 (sizebuf_t *sb, float f) +{ + MSG_WriteShort (sb, f); + MSG_WriteByte (sb, (int)(f*255)%255); +} + +//johnfitz -- 32-bit float coords +void MSG_WriteCoord32f (sizebuf_t *sb, float f) +{ + MSG_WriteFloat (sb, f); +} + +void MSG_WriteCoord (sizebuf_t *sb, float f, unsigned int flags) +{ + if (flags & PRFL_FLOATCOORD) + MSG_WriteFloat (sb, f); + else if (flags & PRFL_INT32COORD) + MSG_WriteLong (sb, Q_rint (f * 16)); + else if (flags & PRFL_24BITCOORD) + MSG_WriteCoord24 (sb, f); + else MSG_WriteCoord16 (sb, f); +} + +void MSG_WriteAngle (sizebuf_t *sb, float f, unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + MSG_WriteFloat (sb, f); + else if (flags & PRFL_SHORTANGLE) + MSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535); + else MSG_WriteByte (sb, Q_rint(f * 256.0 / 360.0) & 255); //johnfitz -- use Q_rint instead of (int) } +} + +//johnfitz -- for PROTOCOL_FITZQUAKE +void MSG_WriteAngle16 (sizebuf_t *sb, float f, unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + MSG_WriteFloat (sb, f); + else MSG_WriteShort (sb, Q_rint(f * 65536.0 / 360.0) & 65535); +} +//johnfitz + +// +// reading functions +// +int msg_readcount; +qboolean msg_badread; + +void MSG_BeginReading (void) +{ + msg_readcount = 0; + msg_badread = false; +} + +// returns -1 and sets msg_badread if no more characters are available +int MSG_ReadChar (void) +{ + int c; + + if (msg_readcount+1 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = (signed char)net_message.data[msg_readcount]; + msg_readcount++; + + return c; +} + +int MSG_ReadByte (void) +{ + int c; + + if (msg_readcount+1 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = (unsigned char)net_message.data[msg_readcount]; + msg_readcount++; + + return c; +} + +int MSG_ReadShort (void) +{ + int c; + + if (msg_readcount+2 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = (short)(net_message.data[msg_readcount] + + (net_message.data[msg_readcount+1]<<8)); + + msg_readcount += 2; + + return c; +} + +int MSG_ReadLong (void) +{ + int c; + + if (msg_readcount+4 > net_message.cursize) + { + msg_badread = true; + return -1; + } + + c = net_message.data[msg_readcount] + + (net_message.data[msg_readcount+1]<<8) + + (net_message.data[msg_readcount+2]<<16) + + (net_message.data[msg_readcount+3]<<24); + + msg_readcount += 4; + + return c; +} + +float MSG_ReadFloat (void) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = net_message.data[msg_readcount]; + dat.b[1] = net_message.data[msg_readcount+1]; + dat.b[2] = net_message.data[msg_readcount+2]; + dat.b[3] = net_message.data[msg_readcount+3]; + msg_readcount += 4; + + dat.l = LittleLong (dat.l); + + return dat.f; +} + +const char *MSG_ReadString (void) +{ + static char string[2048]; + int c; + size_t l; + + l = 0; + do + { + c = MSG_ReadByte (); + if (c == -1 || c == 0) + break; + string[l] = c; + l++; + } while (l < sizeof(string) - 1); + + string[l] = 0; + + return string; +} + +//johnfitz -- original behavior, 13.3 fixed point coords, max range +-4096 +float MSG_ReadCoord16 (void) +{ + return MSG_ReadShort() * (1.0/8); +} + +//johnfitz -- 16.8 fixed point coords, max range +-32768 +float MSG_ReadCoord24 (void) +{ + return MSG_ReadShort() + MSG_ReadByte() * (1.0/255); +} + +//johnfitz -- 32-bit float coords +float MSG_ReadCoord32f (void) +{ + return MSG_ReadFloat(); +} + +float MSG_ReadCoord (unsigned int flags) +{ + if (flags & PRFL_FLOATCOORD) + return MSG_ReadFloat (); + else if (flags & PRFL_INT32COORD) + return MSG_ReadLong () * (1.0 / 16.0); + else if (flags & PRFL_24BITCOORD) + return MSG_ReadCoord24 (); + else return MSG_ReadCoord16 (); +} + +float MSG_ReadAngle (unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + return MSG_ReadFloat (); + else if (flags & PRFL_SHORTANGLE) + return MSG_ReadShort () * (360.0 / 65536); + else return MSG_ReadChar () * (360.0 / 256); +} + +//johnfitz -- for PROTOCOL_FITZQUAKE +float MSG_ReadAngle16 (unsigned int flags) +{ + if (flags & PRFL_FLOATANGLE) + return MSG_ReadFloat (); // make sure + else return MSG_ReadShort () * (360.0 / 65536); +} +//johnfitz + +//=========================================================================== + +void SZ_Alloc (sizebuf_t *buf, int startsize) +{ + if (startsize < 256) + startsize = 256; + buf->data = (byte *) Hunk_AllocName (startsize, "sizebuf"); + buf->maxsize = startsize; + buf->cursize = 0; +} + + +void SZ_Free (sizebuf_t *buf) +{ +// Z_Free (buf->data); +// buf->data = NULL; +// buf->maxsize = 0; + buf->cursize = 0; +} + +void SZ_Clear (sizebuf_t *buf) +{ + buf->cursize = 0; +} + +void *SZ_GetSpace (sizebuf_t *buf, int length) +{ + void *data; + + if (buf->cursize + length > buf->maxsize) + { + if (!buf->allowoverflow) + Host_Error ("SZ_GetSpace: overflow without allowoverflow set"); // ericw -- made Host_Error to be less annoying + + if (length > buf->maxsize) + Sys_Error ("SZ_GetSpace: %i is > full buffer size", length); + + buf->overflowed = true; + Con_Printf ("SZ_GetSpace: overflow\n"); + SZ_Clear (buf); + } + + data = buf->data + buf->cursize; + buf->cursize += length; + + return data; +} + +void SZ_Write (sizebuf_t *buf, const void *data, int length) +{ + Q_memcpy (SZ_GetSpace(buf,length),data,length); +} + +void SZ_Print (sizebuf_t *buf, const char *data) +{ + int len = Q_strlen(data) + 1; + + if (buf->data[buf->cursize-1]) + { /* no trailing 0 */ + Q_memcpy ((byte *)SZ_GetSpace(buf, len ) , data, len); + } + else + { /* write over trailing 0 */ + Q_memcpy ((byte *)SZ_GetSpace(buf, len-1)-1, data, len); + } +} + + +//============================================================================ + +/* +============ +COM_SkipPath +============ +*/ +const char *COM_SkipPath (const char *pathname) +{ + const char *last; + + last = pathname; + while (*pathname) + { + if (*pathname == '/') + last = pathname + 1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (const char *in, char *out, size_t outsize) +{ + int length; + + if (!*in) + { + *out = '\0'; + return; + } + if (in != out) /* copy when not in-place editing */ + q_strlcpy (out, in, outsize); + length = (int)strlen(out) - 1; + while (length > 0 && out[length] != '.') + { + --length; + if (out[length] == '/' || out[length] == '\\') + return; /* no extension */ + } + if (length > 0) + out[length] = '\0'; +} + +/* +============ +COM_FileGetExtension - doesn't return NULL +============ +*/ +const char *COM_FileGetExtension (const char *in) +{ + const char *src; + size_t len; + + len = strlen(in); + if (len < 2) /* nothing meaningful */ + return ""; + + src = in + len - 1; + while (src != in && src[-1] != '.') + src--; + if (src == in || strchr(src, '/') != NULL || strchr(src, '\\') != NULL) + return ""; /* no extension, or parent directory has a dot */ + + return src; +} + +/* +============ +COM_ExtractExtension +============ +*/ +void COM_ExtractExtension (const char *in, char *out, size_t outsize) +{ + const char *ext = COM_FileGetExtension (in); + if (! *ext) + *out = '\0'; + else + q_strlcpy (out, ext, outsize); +} + +/* +============ +COM_FileBase +take 'somedir/otherdir/filename.ext', +write only 'filename' to the output +============ +*/ +void COM_FileBase (const char *in, char *out, size_t outsize) +{ + const char *dot, *slash, *s; + + s = in; + slash = in; + dot = NULL; + while (*s) + { + if (*s == '/') + slash = s + 1; + if (*s == '.') + dot = s; + s++; + } + if (dot == NULL) + dot = s; + + if (dot - slash < 2) + q_strlcpy (out, "?model?", outsize); + else + { + size_t len = dot - slash; + if (len >= outsize) + len = outsize - 1; + memcpy (out, slash, len); + out[len] = '\0'; + } +} + +/* +================== +COM_DefaultExtension +if path doesn't have a .EXT, append extension +(extension should include the leading ".") +================== +*/ +#if 0 /* can be dangerous */ +void COM_DefaultExtension (char *path, const char *extension, size_t len) +{ + char *src; + + if (!*path) return; + src = path + strlen(path) - 1; + + while (*src != '/' && *src != '\\' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + q_strlcat(path, extension, len); +} +#endif + +/* +================== +COM_AddExtension +if path extension doesn't match .EXT, append it +(extension should include the leading ".") +================== +*/ +void COM_AddExtension (char *path, const char *extension, size_t len) +{ + if (strcmp(COM_FileGetExtension(path), extension + 1) != 0) + q_strlcat(path, extension, len); +} + + +/* +============== +COM_ParseEx + +Parse a token out of a string + +The mode argument controls how overflow is handled: +- CPE_NOTRUNC: return NULL (abort parsing) +- CPE_ALLOWTRUNC: truncate com_token (ignore the extra characters in this token) +============== +*/ +const char *COM_ParseEx (const char *data, cpe_mode mode) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ((c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file + data++; + } + +// skip // comments + if (c == '/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + +// skip /*..*/ comments + if (c == '/' && data[1] == '*') + { + data += 2; + while (*data && !(*data == '*' && data[1] == '/')) + data++; + if (*data) + data += 2; + goto skipwhite; + } + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + if ((c = *data) != 0) + ++data; + if (c == '\"' || !c) + { + com_token[len] = 0; + return data; + } + if (len < Q_COUNTOF(com_token) - 1) + com_token[len++] = c; + else if (mode == CPE_NOTRUNC) + return NULL; + } + } + +// parse single characters + if (c == '{' || c == '}'|| c == '('|| c == ')' || c == '\'' || c == ':') + { + if (len < Q_COUNTOF(com_token) - 1) + com_token[len++] = c; + else if (mode == CPE_NOTRUNC) + return NULL; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + if (len < Q_COUNTOF(com_token) - 1) + com_token[len++] = c; + else if (mode == CPE_NOTRUNC) + return NULL; + data++; + c = *data; + /* commented out the check for ':' so that ip:port works */ + if (c == '{' || c == '}'|| c == '('|| c == ')' || c == '\''/* || c == ':' */) + break; + } while (c > 32); + + com_token[len] = 0; + return data; +} + + +/* +============== +COM_Parse + +Parse a token out of a string + +Return NULL in case of overflow +============== +*/ +const char *COM_Parse (const char *data) +{ + return COM_ParseEx (data, CPE_NOTRUNC); +} + + +/* +================ +COM_CheckParm + +Returns the position (1 to argc-1) in the program's argument list +where the given parameter apears, or 0 if not present +================ +*/ +int COM_CheckParm (const char *parm) +{ + int i; + + for (i = 1; i < com_argc; i++) + { + if (!com_argv[i]) + continue; // NEXTSTEP sometimes clears appkit vars. + if (!Q_strcmp (parm,com_argv[i])) + return i; + } + + return 0; +} + +/* +================ +COM_CheckRegistered + +Looks for the pop.txt file and verifies it. +Sets the "registered" cvar. +Immediately exits out if an alternate game was attempted to be started without +being registered. +================ +*/ +static void COM_CheckRegistered (void) +{ + int h; + unsigned short check[128]; + int i; + + COM_OpenFile("gfx/pop.lmp", &h, NULL); + + if (h == -1) + { + Cvar_SetROM ("registered", "0"); + Con_Printf ("Playing shareware version.\n"); + if (com_modified) + Sys_Error ("You must have the registered version to use modified games.\n\n" + "Basedir is: %s\n\n" + "Check that this has an " GAMENAME " subdirectory containing pak0.pak and pak1.pak, " + "or use the -basedir command-line option to specify another directory.", + com_basedir); + return; + } + + i = Sys_FileRead (h, check, sizeof(check)); + COM_CloseFile (h); + if (i != (int) sizeof(check)) + goto corrupt; + + for (i = 0; i < 128; i++) + { + if (pop[i] != (unsigned short)BigShort (check[i])) + { corrupt: + Sys_Error ("Corrupted data file."); + } + } + + for (i = 0; com_cmdline[i]; i++) + { + if (com_cmdline[i]!= ' ') + break; + } + + Cvar_SetROM ("cmdline", &com_cmdline[i]); + Cvar_SetROM ("registered", "1"); + Con_Printf ("Playing registered version.\n"); +} + + +/* +================ +COM_InitArgv +================ +*/ +void COM_InitArgv (int argc, char **argv) +{ + int i, j, n; + +// reconstitute the command line for the cmdline externally visible cvar + n = 0; + + for (j = 0; (j 0 && com_cmdline[n-1] == ' ') + com_cmdline[n-1] = 0; //johnfitz -- kill the trailing space + + Con_Printf("Command line: %s\n", com_cmdline); + + for (com_argc = 0; (com_argc < MAX_NUM_ARGVS) && (com_argc < argc); com_argc++) + { + largv[com_argc] = argv[com_argc]; + if (!Q_strcmp ("-safe", argv[com_argc])) + safemode = 1; + } + + largv[com_argc] = argvdummy; + com_argv = largv; + + if (COM_CheckParm ("-rogue")) + { + rogue = true; + standard_quake = false; + } + + if (COM_CheckParm ("-hipnotic") || COM_CheckParm ("-quoth")) //johnfitz -- "-quoth" support + { + hipnotic = true; + standard_quake = false; + } +} + +/* +================ +COM_Init +================ +*/ +void COM_Init (void) +{ + int i = 0x12345678; + /* U N I X */ + + /* + BE_ORDER: 12 34 56 78 + U N I X + + LE_ORDER: 78 56 34 12 + X I N U + + PDP_ORDER: 34 12 78 56 + N U X I + */ + if ( *(char *)&i == 0x12 ) + host_bigendian = true; + else if ( *(char *)&i == 0x78 ) + host_bigendian = false; + else /* if ( *(char *)&i == 0x34 ) */ + Sys_Error ("Unsupported endianism."); + + if (host_bigendian) + { + BigShort = ShortNoSwap; + LittleShort = ShortSwap; + BigLong = LongNoSwap; + LittleLong = LongSwap; + BigFloat = FloatNoSwap; + LittleFloat = FloatSwap; + } + else /* assumed LITTLE_ENDIAN. */ + { + BigShort = ShortSwap; + LittleShort = ShortNoSwap; + BigLong = LongSwap; + LittleLong = LongNoSwap; + BigFloat = FloatSwap; + LittleFloat = FloatNoSwap; + } + + if (COM_CheckParm("-fitz")) + fitzmode = true; +} + + +/* +============ +va + +does a varargs printf into a temp buffer. cycles between +4 different static buffers. the number of buffers cycled +is defined in VA_NUM_BUFFS. +FIXME: make this buffer size safe someday +============ +*/ +#define VA_NUM_BUFFS 4 +#define VA_BUFFERLEN 1024 + +static char *get_va_buffer(void) +{ + static char va_buffers[VA_NUM_BUFFS][VA_BUFFERLEN]; + static int buffer_idx = 0; + buffer_idx = (buffer_idx + 1) & (VA_NUM_BUFFS - 1); + return va_buffers[buffer_idx]; +} + +char *va (const char *format, ...) +{ + va_list argptr; + char *va_buf; + + va_buf = get_va_buffer (); + va_start (argptr, format); + q_vsnprintf (va_buf, VA_BUFFERLEN, format, argptr); + va_end (argptr); + + return va_buf; +} + +/* +============================================================================= + +QUAKE FILESYSTEM + +============================================================================= +*/ + +int com_filesize; + + +// +// on-disk pakfile +// +typedef struct +{ + char name[56]; + int filepos, filelen; +} dpackfile_t; + +typedef struct +{ + char id[4]; + int dirofs; + int dirlen; +} dpackheader_t; + +#define MAX_FILES_IN_PACK 2048 + +char com_gamedir[MAX_OSPATH]; +char com_basedir[MAX_OSPATH]; +int file_from_pak; // ZOID: global indicating that file came from a pak + +searchpath_t *com_searchpaths; +searchpath_t *com_base_searchpaths; + +/* +============ +COM_Path_f +============ +*/ +static void COM_Path_f (void) +{ + searchpath_t *s; + + Con_Printf ("Current search path:\n"); + for (s = com_searchpaths; s; s = s->next) + { + if (s->pack) + { + Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles); + } + else + Con_Printf ("%s\n", s->filename); + } +} + +/* +============ +COM_WriteFile + +The filename will be prefixed by the current game directory +============ +*/ +void COM_WriteFile (const char *filename, const void *data, int len) +{ + int handle; + char name[MAX_OSPATH]; + + Sys_mkdir (com_gamedir); //johnfitz -- if we've switched to a nonexistant gamedir, create it now so we don't crash + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, filename); + + handle = Sys_FileOpenWrite (name); + if (handle == -1) + { + Sys_Printf ("COM_WriteFile: failed on %s\n", name); + return; + } + + Sys_Printf ("COM_WriteFile: %s\n", name); + Sys_FileWrite (handle, data, len); + Sys_FileClose (handle); +} + +/* +============ +COM_CreatePath +============ +*/ +void COM_CreatePath (char *path) +{ + char *ofs; + + for (ofs = path + 1; *ofs; ofs++) + { + if (*ofs == '/') + { // create the directory + *ofs = 0; + Sys_mkdir (path); + *ofs = '/'; + } + } +} + +/* +================ +COM_filelength +================ +*/ +long COM_filelength (FILE *f) +{ + long pos, end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +/* +=========== +COM_FindFile + +Finds the file in the search path. +Sets com_filesize and one of handle or file +If neither of file or handle is set, this +can be used for detecting a file's presence. +=========== +*/ +static int COM_FindFile (const char *filename, int *handle, FILE **file, + unsigned int *path_id) +{ + searchpath_t *search; + char netpath[MAX_OSPATH]; + pack_t *pak; + int i; + + if (file && handle) + Sys_Error ("COM_FindFile: both handle and file set"); + + file_from_pak = 0; + +// +// search through the path, one element at a time +// + for (search = com_searchpaths; search; search = search->next) + { + if (search->pack) /* look through all the pak file elements */ + { + pak = search->pack; + for (i = 0; i < pak->numfiles; i++) + { + if (strcmp(pak->files[i].name, filename) != 0) + continue; + // found it! + com_filesize = pak->files[i].filelen; + file_from_pak = 1; + if (path_id) + *path_id = search->path_id; + if (handle) + { + *handle = pak->handle; + Sys_FileSeek (pak->handle, pak->files[i].filepos); + return com_filesize; + } + else if (file) + { /* open a new file on the pakfile */ + *file = fopen (pak->filename, "rb"); + if (*file) + fseek (*file, pak->files[i].filepos, SEEK_SET); + return com_filesize; + } + else /* for COM_FileExists() */ + { + return com_filesize; + } + } + } + else /* check a file in the directory tree */ + { + if (!registered.value) + { /* if not a registered version, don't ever go beyond base */ + if ( strchr (filename, '/') || strchr (filename,'\\')) + continue; + } + + q_snprintf (netpath, sizeof(netpath), "%s/%s",search->filename, filename); + if (! (Sys_FileType(netpath) & FS_ENT_FILE)) + continue; + + if (path_id) + *path_id = search->path_id; + if (handle) + { + com_filesize = Sys_FileOpenRead (netpath, &i); + *handle = i; + return com_filesize; + } + else if (file) + { + *file = fopen (netpath, "rb"); + com_filesize = (*file == NULL) ? -1 : COM_filelength (*file); + return com_filesize; + } + else + { + return 0; /* dummy valid value for COM_FileExists() */ + } + } + } + + if (strcmp(COM_FileGetExtension(filename), "pcx") != 0 + && strcmp(COM_FileGetExtension(filename), "tga") != 0 + && strcmp(COM_FileGetExtension(filename), "lit") != 0 + && strcmp(COM_FileGetExtension(filename), "vis") != 0 + && strcmp(COM_FileGetExtension(filename), "ent") != 0) + Con_DPrintf ("FindFile: can't find %s\n", filename); + else Con_DPrintf2("FindFile: can't find %s\n", filename); + + if (handle) + *handle = -1; + if (file) + *file = NULL; + com_filesize = -1; + return com_filesize; +} + + +/* +=========== +COM_FileExists + +Returns whether the file is found in the quake filesystem. +=========== +*/ +qboolean COM_FileExists (const char *filename, unsigned int *path_id) +{ + int ret = COM_FindFile (filename, NULL, NULL, path_id); + return (ret == -1) ? false : true; +} + +/* +=========== +COM_OpenFile + +filename never has a leading slash, but may contain directory walks +returns a handle and a length +it may actually be inside a pak file +=========== +*/ +int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id) +{ + return COM_FindFile (filename, handle, NULL, path_id); +} + +/* +=========== +COM_FOpenFile + +If the requested file is inside a packfile, a new FILE * will be opened +into the file. +=========== +*/ +int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id) +{ + return COM_FindFile (filename, NULL, file, path_id); +} + +/* +============ +COM_CloseFile + +If it is a pak file handle, don't really close it +============ +*/ +void COM_CloseFile (int h) +{ + searchpath_t *s; + + for (s = com_searchpaths; s; s = s->next) + if (s->pack && s->pack->handle == h) + return; + + Sys_FileClose (h); +} + + +/* +============ +COM_LoadFile + +Filename are reletive to the quake directory. +Allways appends a 0 byte. +============ +*/ +#define LOADFILE_ZONE 0 +#define LOADFILE_HUNK 1 +#define LOADFILE_TEMPHUNK 2 +#define LOADFILE_CACHE 3 +#define LOADFILE_STACK 4 +#define LOADFILE_MALLOC 5 + +static byte *loadbuf; +static cache_user_t *loadcache; +static int loadsize; + +byte *COM_LoadFile (const char *path, int usehunk, unsigned int *path_id) +{ + int h; + byte *buf; + char base[32]; + int len, nread; + + buf = NULL; // quiet compiler warning + +// look for it in the filesystem or pack files + len = COM_OpenFile (path, &h, path_id); + if (h == -1) + return NULL; + +// extract the filename base name for hunk tag + COM_FileBase (path, base, sizeof(base)); + + switch (usehunk) + { + case LOADFILE_HUNK: + buf = (byte *) Hunk_AllocName (len+1, base); + break; + case LOADFILE_TEMPHUNK: + buf = (byte *) Hunk_TempAlloc (len+1); + break; + case LOADFILE_ZONE: + buf = (byte *) Z_Malloc (len+1); + break; + case LOADFILE_CACHE: + buf = (byte *) Cache_Alloc (loadcache, len+1, base); + break; + case LOADFILE_STACK: + if (len < loadsize) + buf = loadbuf; + else + buf = (byte *) Hunk_TempAlloc (len+1); + break; + case LOADFILE_MALLOC: + buf = (byte *) malloc (len+1); + break; + default: + Sys_Error ("COM_LoadFile: bad usehunk"); + } + + if (!buf) + Sys_Error ("COM_LoadFile: not enough space for %s", path); + + ((byte *)buf)[len] = 0; + + nread = Sys_FileRead (h, buf, len); + COM_CloseFile (h); + if (nread != len) + Sys_Error ("COM_LoadFile: Error reading %s", path); + + return buf; +} + +byte *COM_LoadHunkFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_HUNK, path_id); +} + +byte *COM_LoadZoneFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_ZONE, path_id); +} + +byte *COM_LoadTempFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_TEMPHUNK, path_id); +} + +void COM_LoadCacheFile (const char *path, struct cache_user_s *cu, unsigned int *path_id) +{ + loadcache = cu; + COM_LoadFile (path, LOADFILE_CACHE, path_id); +} + +// uses temp hunk if larger than bufsize +byte *COM_LoadStackFile (const char *path, void *buffer, int bufsize, unsigned int *path_id) +{ + byte *buf; + + loadbuf = (byte *)buffer; + loadsize = bufsize; + buf = COM_LoadFile (path, LOADFILE_STACK, path_id); + + return buf; +} + +// returns malloc'd memory +byte *COM_LoadMallocFile (const char *path, unsigned int *path_id) +{ + return COM_LoadFile (path, LOADFILE_MALLOC, path_id); +} + +byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out) +{ + FILE *f; + byte *data; + long len, actuallen; + + // ericw -- this is used by Host_Loadgame_f. Translate CRLF to LF on load games, + // othewise multiline messages have a garbage character at the end of each line. + // TODO: could handle in a way that allows loading CRLF savegames on mac/linux + // without the junk characters appearing. + f = fopen (path, "rt"); + if (f == NULL) + return NULL; + + len = COM_filelength (f); + if (len < 0) + { + fclose (f); + return NULL; + } + + data = (byte *) malloc (len + 1); + if (data == NULL) + { + fclose (f); + return NULL; + } + + // (actuallen < len) if CRLF to LF translation was performed + actuallen = fread (data, 1, len, f); + if (ferror(f)) + { + fclose (f); + free (data); + return NULL; + } + data[actuallen] = '\0'; + + if (len_out != NULL) + *len_out = actuallen; + fclose (f); + return data; +} + +const char *COM_ParseIntNewline(const char *buffer, int *value) +{ + int consumed = 0; + sscanf (buffer, "%i\n%n", value, &consumed); + return buffer + consumed; +} + +const char *COM_ParseFloatNewline(const char *buffer, float *value) +{ + int consumed = 0; + sscanf (buffer, "%f\n%n", value, &consumed); + return buffer + consumed; +} + +const char *COM_ParseStringNewline(const char *buffer) +{ + int consumed = 0; + com_token[0] = '\0'; + sscanf (buffer, "%1023s\n%n", com_token, &consumed); + return buffer + consumed; +} + +/* +================= +COM_LoadPackFile -- johnfitz -- modified based on topaz's tutorial + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +static pack_t *COM_LoadPackFile (const char *packfile) +{ + dpackheader_t header; + int i; + packfile_t *newfiles; + int numpackfiles; + pack_t *pack; + int packhandle; + dpackfile_t info[MAX_FILES_IN_PACK]; + unsigned short crc; + + if (Sys_FileOpenRead (packfile, &packhandle) == -1) + return NULL; + + if (Sys_FileRead(packhandle, &header, sizeof(header)) != (int) sizeof(header) || + header.id[0] != 'P' || header.id[1] != 'A' || header.id[2] != 'C' || header.id[3] != 'K') + Sys_Error ("%s is not a packfile", packfile); + + header.dirofs = LittleLong (header.dirofs); + header.dirlen = LittleLong (header.dirlen); + + numpackfiles = header.dirlen / sizeof(dpackfile_t); + + if (header.dirlen < 0 || header.dirofs < 0) + { + Sys_Error ("Invalid packfile %s (dirlen: %i, dirofs: %i)", + packfile, header.dirlen, header.dirofs); + } + if (!numpackfiles) + { + Sys_Printf ("WARNING: %s has no files, ignored\n", packfile); + Sys_FileClose (packhandle); + return NULL; + } + if (numpackfiles > MAX_FILES_IN_PACK) + Sys_Error ("%s has %i files", packfile, numpackfiles); + + if (numpackfiles != PAK0_COUNT) + com_modified = true; // not the original file + + newfiles = (packfile_t *) Z_Malloc(numpackfiles * sizeof(packfile_t)); + + Sys_FileSeek (packhandle, header.dirofs); + if (Sys_FileRead(packhandle, info, header.dirlen) != header.dirlen) + Sys_Error ("Error reading %s", packfile); + + // crc the directory to check for modifications + CRC_Init (&crc); + for (i = 0; i < header.dirlen; i++) + CRC_ProcessByte (&crc, ((byte *)info)[i]); + if (crc != PAK0_CRC_V106 && crc != PAK0_CRC_V101 && crc != PAK0_CRC_V100) + com_modified = true; + + // parse the directory + for (i = 0; i < numpackfiles; i++) + { + q_strlcpy (newfiles[i].name, info[i].name, sizeof(newfiles[i].name)); + newfiles[i].filepos = LittleLong(info[i].filepos); + newfiles[i].filelen = LittleLong(info[i].filelen); + } + + pack = (pack_t *) Z_Malloc (sizeof (pack_t)); + q_strlcpy (pack->filename, packfile, sizeof(pack->filename)); + pack->handle = packhandle; + pack->numfiles = numpackfiles; + pack->files = newfiles; + + //Sys_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles); + return pack; +} + +/* +================= +COM_AddGameDirectory -- johnfitz -- modified based on topaz's tutorial +================= +*/ +static void COM_AddGameDirectory (const char *base, const char *dir) +{ + int i; + unsigned int path_id; + searchpath_t *search; + pack_t *pak, *qspak; + char pakfile[MAX_OSPATH]; + qboolean been_here = false; + + q_strlcpy (com_gamedir, va("%s/%s", base, dir), sizeof(com_gamedir)); + + // assign a path_id to this game directory + if (com_searchpaths) + path_id = com_searchpaths->path_id << 1; + else path_id = 1U; + +_add_path: + // add the directory to the search path + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = path_id; + q_strlcpy (search->filename, com_gamedir, sizeof(search->filename)); + search->next = com_searchpaths; + com_searchpaths = search; + + // add any pak files in the format pak0.pak pak1.pak, ... + for (i = 0; ; i++) + { + q_snprintf (pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i); + pak = COM_LoadPackFile (pakfile); + if (i != 0 || path_id != 1 || fitzmode) + qspak = NULL; + else { + qboolean old = com_modified; + if (been_here) base = host_parms->userdir; + q_snprintf (pakfile, sizeof(pakfile), "%s/quakespasm.pak", base); + qspak = COM_LoadPackFile (pakfile); + com_modified = old; + } + if (pak) { + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = path_id; + search->pack = pak; + search->next = com_searchpaths; + com_searchpaths = search; + } + if (qspak) { + search = (searchpath_t *) Z_Malloc(sizeof(searchpath_t)); + search->path_id = path_id; + search->pack = qspak; + search->next = com_searchpaths; + com_searchpaths = search; + } + if (!pak) break; + } + + if (!been_here && host_parms->userdir != host_parms->basedir) + { + been_here = true; + q_strlcpy(com_gamedir, va("%s/%s", host_parms->userdir, dir), sizeof(com_gamedir)); + Sys_mkdir(com_gamedir); + goto _add_path; + } +} + +//============================================================================== +//johnfitz -- dynamic gamedir stuff -- modified by QuakeSpasm team. +//============================================================================== +static void COM_Game_f (void) +{ + if (Cmd_Argc() > 1) + { + const char *p = Cmd_Argv(1); + const char *p2 = Cmd_Argv(2); + searchpath_t *search; + + if (!registered.value) //disable shareware quake + { + Con_Printf("You must have the registered version to use modified games\n"); + return; + } + + if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + { + Con_Printf ("gamedir should be a single directory name, not a path\n"); + return; + } + + if (*p2) + { + if (strcmp(p2,"-hipnotic") && strcmp(p2,"-rogue") && strcmp(p2,"-quoth")) { + Con_Printf ("invalid mission pack argument to \"game\"\n"); + return; + } + if (!q_strcasecmp(p, GAMENAME)) { + Con_Printf ("no mission pack arguments to %s game\n", GAMENAME); + return; + } + } + + if (Sys_FileType(va("%s/%s", com_basedir, p)) != FS_ENT_DIRECTORY) + { + if (host_parms->userdir == host_parms->basedir || (Sys_FileType(va("%s/%s", host_parms->userdir, p)) != FS_ENT_DIRECTORY)) + { + Con_Printf ("No such game directory \"%s\"\n", p); + return; + } + } + + if (!q_strcasecmp(p, COM_SkipPath(com_gamedir))) //no change + { + if (com_searchpaths->path_id > 1) { //current game not id1 + if (*p2 && com_searchpaths->path_id == 2) { + // rely on QuakeSpasm extension treating '-game missionpack' + // as '-missionpack', otherwise would be a mess + if (!q_strcasecmp(p, &p2[1])) + goto _same; + Con_Printf("reloading game \"%s\" with \"%s\" support\n", p, &p2[1]); + } + else if (!*p2 && com_searchpaths->path_id > 2) + Con_Printf("reloading game \"%s\" without mission pack support\n", p); + else goto _same; + } + else { _same: + Con_Printf("\"game\" is already \"%s\"\n", COM_SkipPath(com_gamedir)); + return; + } + } + + com_modified = true; + + //Kill the server + CL_Disconnect (); + Host_ShutdownServer(true); + + //Write config file + Host_WriteConfiguration (); + + //Kill the extra game if it is loaded + while (com_searchpaths != com_base_searchpaths) + { + if (com_searchpaths->pack) + { + Sys_FileClose (com_searchpaths->pack->handle); + Z_Free (com_searchpaths->pack->files); + Z_Free (com_searchpaths->pack); + } + search = com_searchpaths->next; + Z_Free (com_searchpaths); + com_searchpaths = search; + } + hipnotic = false; + rogue = false; + standard_quake = true; + + if (q_strcasecmp(p, GAMENAME)) //game is not id1 + { + if (*p2) { + COM_AddGameDirectory (com_basedir, &p2[1]); + standard_quake = false; + if (!strcmp(p2,"-hipnotic") || !strcmp(p2,"-quoth")) + hipnotic = true; + else if (!strcmp(p2,"-rogue")) + rogue = true; + if (q_strcasecmp(p, &p2[1])) //don't load twice + COM_AddGameDirectory (com_basedir, p); + } + else { + COM_AddGameDirectory (com_basedir, p); + // QuakeSpasm extension: treat '-game missionpack' as '-missionpack' + if (!q_strcasecmp(p,"hipnotic") || !q_strcasecmp(p,"quoth")) { + hipnotic = true; + standard_quake = false; + } + else if (!q_strcasecmp(p,"rogue")) { + rogue = true; + standard_quake = false; + } + } + } + else // just update com_gamedir + { + q_snprintf (com_gamedir, sizeof(com_gamedir), "%s/%s", + (host_parms->userdir != host_parms->basedir)? + host_parms->userdir : com_basedir, + GAMENAME); + } + + //clear out and reload appropriate data + Cache_Flush (); + Mod_ResetAll(); + Sky_ClearAll(); + if (!isDedicated) + { + TexMgr_NewGame (); + Draw_NewGame (); + R_NewGame (); + } + ExtraMaps_NewGame (); + Host_Resetdemos (); + DemoList_Rebuild (); + + Con_Printf("\"game\" changed to \"%s\"\n", COM_SkipPath(com_gamedir)); + + VID_Lock (); + Cbuf_AddText ("exec quake.rc\n"); + Cbuf_AddText ("vid_unlock\n"); + } + else //Diplay the current gamedir + Con_Printf("\"game\" is \"%s\"\n", COM_SkipPath(com_gamedir)); +} + +/* +================= +COM_InitFilesystem +================= +*/ +void COM_InitFilesystem (void) //johnfitz -- modified based on topaz's tutorial +{ + int i, j; + + Cvar_RegisterVariable (®istered); + Cvar_RegisterVariable (&cmdline); + Cmd_AddCommand ("path", COM_Path_f); + Cmd_AddCommand ("game", COM_Game_f); //johnfitz + + i = COM_CheckParm ("-basedir"); + if (i && i < com_argc-1) + q_strlcpy (com_basedir, com_argv[i + 1], sizeof(com_basedir)); + else + q_strlcpy (com_basedir, host_parms->basedir, sizeof(com_basedir)); + + j = strlen (com_basedir); + if (j < 1) Sys_Error("Bad argument to -basedir"); + if ((com_basedir[j-1] == '\\') || (com_basedir[j-1] == '/')) + com_basedir[j-1] = 0; + + // start up with GAMENAME by default (id1) + COM_AddGameDirectory (com_basedir, GAMENAME); + + /* this is the end of our base searchpath: + * any set gamedirs, such as those from -game command line + * arguments or by the 'game' console command will be freed + * up to here upon a new game command. */ + com_base_searchpaths = com_searchpaths; + + // add mission pack requests (only one should be specified) + if (COM_CheckParm ("-rogue")) + COM_AddGameDirectory (com_basedir, "rogue"); + if (COM_CheckParm ("-hipnotic")) + COM_AddGameDirectory (com_basedir, "hipnotic"); + if (COM_CheckParm ("-quoth")) + COM_AddGameDirectory (com_basedir, "quoth"); + + i = COM_CheckParm ("-game"); + if (i && i < com_argc-1) + { + const char *p = com_argv[i + 1]; + if (!*p || !strcmp(p, ".") || strstr(p, "..") || strstr(p, "/") || strstr(p, "\\") || strstr(p, ":")) + Sys_Error ("gamedir should be a single directory name, not a path\n"); + com_modified = true; + // don't load mission packs twice + if (COM_CheckParm ("-rogue") && !q_strcasecmp(p, "rogue")) p = NULL; + if (p && COM_CheckParm ("-hipnotic") && !q_strcasecmp(p, "hipnotic")) p = NULL; + if (p && COM_CheckParm ("-quoth") && !q_strcasecmp(p, "quoth")) p = NULL; + if (p != NULL) { + COM_AddGameDirectory (com_basedir, p); + // QuakeSpasm extension: treat '-game missionpack' as '-missionpack' + if (!q_strcasecmp(p,"rogue")) { + rogue = true; + standard_quake = false; + } + if (!q_strcasecmp(p,"hipnotic") || !q_strcasecmp(p,"quoth")) { + hipnotic = true; + standard_quake = false; + } + } + } + + COM_CheckRegistered (); +} + + +/* The following FS_*() stdio replacements are necessary if one is + * to perform non-sequential reads on files reopened on pak files + * because we need the bookkeeping about file start/end positions. + * Allocating and filling in the fshandle_t structure is the users' + * responsibility when the file is initially opened. */ + +size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh) +{ + long byte_size; + long bytes_read; + size_t nmemb_read; + + if (!fh) { + errno = EBADF; + return 0; + } + if (!ptr) { + errno = EFAULT; + return 0; + } + if (!size || !nmemb) { /* no error, just zero bytes wanted */ + errno = 0; + return 0; + } + + byte_size = nmemb * size; + if (byte_size > fh->length - fh->pos) /* just read to end */ + byte_size = fh->length - fh->pos; + bytes_read = fread(ptr, 1, byte_size, fh->file); + fh->pos += bytes_read; + + /* fread() must return the number of elements read, + * not the total number of bytes. */ + nmemb_read = bytes_read / size; + /* even if the last member is only read partially + * it is counted as a whole in the return value. */ + if (bytes_read % size) + nmemb_read++; + + return nmemb_read; +} + +int FS_fseek(fshandle_t *fh, long offset, int whence) +{ +/* I don't care about 64 bit off_t or fseeko() here. + * the quake/hexen2 file system is 32 bits, anyway. */ + int ret; + + if (!fh) { + errno = EBADF; + return -1; + } + + /* the relative file position shouldn't be smaller + * than zero or bigger than the filesize. */ + switch (whence) + { + case SEEK_SET: + break; + case SEEK_CUR: + offset += fh->pos; + break; + case SEEK_END: + offset = fh->length + offset; + break; + default: + errno = EINVAL; + return -1; + } + + if (offset < 0) { + errno = EINVAL; + return -1; + } + + if (offset > fh->length) /* just seek to end */ + offset = fh->length; + + ret = fseek(fh->file, fh->start + offset, SEEK_SET); + if (ret < 0) + return ret; + + fh->pos = offset; + return 0; +} + +int FS_fclose(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return fclose(fh->file); +} + +long FS_ftell(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return fh->pos; +} + +void FS_rewind(fshandle_t *fh) +{ + if (!fh) return; + clearerr(fh->file); + fseek(fh->file, fh->start, SEEK_SET); + fh->pos = 0; +} + +int FS_feof(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + if (fh->pos >= fh->length) + return -1; + return 0; +} + +int FS_ferror(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return ferror(fh->file); +} + +int FS_fgetc(fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return EOF; + } + if (fh->pos >= fh->length) + return EOF; + fh->pos += 1; + return fgetc(fh->file); +} + +char *FS_fgets(char *s, int size, fshandle_t *fh) +{ + char *ret; + + if (FS_feof(fh)) + return NULL; + + if (size > (fh->length - fh->pos) + 1) + size = (fh->length - fh->pos) + 1; + + ret = fgets(s, size, fh->file); + fh->pos = ftell(fh->file) - fh->start; + + return ret; +} + +long FS_filelength (fshandle_t *fh) +{ + if (!fh) { + errno = EBADF; + return -1; + } + return fh->length; +} + +/* +============================================================================ + LOCALIZATION +============================================================================ +*/ +typedef struct +{ + char *key; + char *value; +} locentry_t; + +typedef struct +{ + int numentries; + int maxnumentries; + int numindices; + unsigned *indices; + locentry_t *entries; + char *text; +} localization_t; + +static localization_t localization; + +/* +================ +COM_HashString +Computes the FNV-1a hash of string str +================ +*/ +unsigned COM_HashString (const char *str) +{ + unsigned hash = 0x811c9dc5u; + while (*str) + { + hash ^= *str++; + hash *= 0x01000193u; + } + return hash; +} + +static size_t mz_zip_file_read_func(void *opaque, mz_uint64 ofs, void *buf, size_t n) +{ + if (SDL_RWseek((SDL_RWops*)opaque, (Sint64)ofs, RW_SEEK_SET) < 0) + return 0; + #ifdef USE_SDL2 + return SDL_RWread((SDL_RWops*)opaque, buf, 1, n); + #else + else { + int r = SDL_RWread((SDL_RWops*)opaque, buf, 1, n); + return (r < 0)? 0 : r; + } + #endif +} + +#ifndef USE_SDL2 /* no SDL_RWsize in SDL-1.2 */ +static Sint32 SDLCALL SDL_RWsize(SDL_RWops *rw) { + Sint32 pos, size; + if ((pos=SDL_RWtell(rw))<0) return -1; + size = SDL_RWseek(rw, 0, RW_SEEK_END); + SDL_RWseek(rw, pos, RW_SEEK_SET); + return size; +} +#endif + +/* +================ +LOC_LoadFile +================ +*/ +void LOC_LoadFile (const char *file) +{ + char path[1024]; + int i,lineno; + char *cursor; + + SDL_RWops *rw = NULL; + Sint64 sz; + mz_zip_archive archive; + size_t size = 0; + + // clear existing data + if (localization.text) + { + free(localization.text); + localization.text = NULL; + } + localization.numentries = 0; + localization.numindices = 0; + + if (!file || !*file) + return; + + Con_Printf("\nLanguage initialization\n"); + + memset(&archive, 0, sizeof(archive)); + q_snprintf(path, sizeof(path), "%s/%s", com_basedir, file); + rw = SDL_RWFromFile(path, "rb"); + #if defined(DO_USERDIRS) + if (!rw) { + q_snprintf(path, sizeof(path), "%s/%s", host_parms->userdir, file); + rw = SDL_RWFromFile(path, "rb"); + } + #endif + if (!rw) + { + q_snprintf(path, sizeof(path), "%s/QuakeEX.kpf", com_basedir); + rw = SDL_RWFromFile(path, "rb"); + #if defined(DO_USERDIRS) + if (!rw) { + q_snprintf(path, sizeof(path), "%s/QuakeEX.kpf", host_parms->userdir); + rw = SDL_RWFromFile(path, "rb"); + } + #endif + if (!rw) goto fail; + sz = SDL_RWsize(rw); + if (sz <= 0) goto fail; + archive.m_pRead = mz_zip_file_read_func; + archive.m_pIO_opaque = rw; + if (!mz_zip_reader_init(&archive, sz, 0)) goto fail; + localization.text = (char *) mz_zip_reader_extract_file_to_heap(&archive, file, &size, 0); + if (!localization.text) goto fail; + mz_zip_reader_end(&archive); + SDL_RWclose(rw); + localization.text = (char *) realloc(localization.text, size+1); + localization.text[size] = 0; + } + else + { + sz = SDL_RWsize(rw); + if (sz <= 0) goto fail; + localization.text = (char *) calloc(1, sz+1); + if (!localization.text) + { +fail: mz_zip_reader_end(&archive); + if (rw) SDL_RWclose(rw); + Con_Printf("Couldn't load '%s'\nfrom '%s'\n", file, com_basedir); + return; + } + SDL_RWread(rw, localization.text, 1, sz); + SDL_RWclose(rw); + } + + cursor = localization.text; + + // skip BOM + if ((unsigned char)(cursor[0]) == 0xEF && (unsigned char)(cursor[1]) == 0xBB && (unsigned char)(cursor[2]) == 0xBF) + cursor += 3; + + lineno = 0; + while (*cursor) + { + char *line, *equals; + + lineno++; + + // skip leading whitespace + while (q_isblank(*cursor)) + ++cursor; + + line = cursor; + equals = NULL; + // find line end and first equals sign, if any + while (*cursor && *cursor != '\n') + { + if (*cursor == '=' && !equals) + equals = cursor; + cursor++; + } + + if (line[0] == '/') + { + if (line[1] != '/') + Con_DPrintf("LOC_LoadFile: malformed comment on line %d\n", lineno); + } + else if (equals) + { + char *key_end = equals; + qboolean leading_quote; + qboolean trailing_quote; + locentry_t *entry; + char *value_src; + char *value_dst; + char *value; + + // trim whitespace before equals sign + while (key_end != line && q_isspace(key_end[-1])) + key_end--; + *key_end = 0; + + value = equals + 1; + // skip whitespace after equals sign + while (value != cursor && q_isspace(*value)) + value++; + + leading_quote = (*value == '\"'); + trailing_quote = false; + value += leading_quote; + + // transform escape sequences in-place + value_src = value; + value_dst = value; + while (value_src != cursor) + { + if (*value_src == '\\' && value_src + 1 != cursor) + { + char c = value_src[1]; + value_src += 2; + switch (c) + { + case 'n': *value_dst++ = '\n'; break; + case 't': *value_dst++ = '\t'; break; + case 'v': *value_dst++ = '\v'; break; + case 'b': *value_dst++ = '\b'; break; + case 'f': *value_dst++ = '\f'; break; + + case '"': + case '\'': + *value_dst++ = c; + break; + + default: + Con_Printf("LOC_LoadFile: unrecognized escape sequence \\%c on line %d\n", c, lineno); + *value_dst++ = c; + break; + } + continue; + } + + if (*value_src == '\"') + { + trailing_quote = true; + *value_dst = 0; + break; + } + + *value_dst++ = *value_src++; + } + + // if not a quoted string, trim trailing whitespace + if (!trailing_quote) + { + while (value_dst != value && q_isblank(value_dst[-1])) + { + *value_dst = 0; + value_dst--; + } + } + + if (localization.numentries == localization.maxnumentries) + { + // grow by 50% + localization.maxnumentries += localization.maxnumentries >> 1; + localization.maxnumentries = q_max(localization.maxnumentries, 32); + localization.entries = (locentry_t*) realloc(localization.entries, sizeof(*localization.entries) * localization.maxnumentries); + } + + entry = &localization.entries[localization.numentries++]; + entry->key = line; + entry->value = value; + } + + if (*cursor) + *cursor++ = 0; // terminate line and advance to next + } + + // hash all entries + + localization.numindices = localization.numentries * 2; // 50% load factor + if (localization.numindices == 0) + { + Con_Printf("No localized strings in file '%s'\n", file); + return; + } + + localization.indices = (unsigned*) realloc(localization.indices, localization.numindices * sizeof(*localization.indices)); + memset(localization.indices, 0, localization.numindices * sizeof(*localization.indices)); + + for (i = 0; i < localization.numentries; i++) + { + locentry_t *entry = &localization.entries[i]; + unsigned pos = COM_HashString(entry->key) % localization.numindices, end = pos; + + for (;;) + { + if (!localization.indices[pos]) + { + localization.indices[pos] = i + 1; + break; + } + + ++pos; + if (pos == localization.numindices) + pos = 0; + + if (pos == end) + Sys_Error("LOC_LoadFile failed"); + } + } + + Con_Printf("Loaded %d strings from '%s'\n", localization.numentries, file); +} + +/* +================ +LOC_Init +================ +*/ +void LOC_Init(void) +{ + LOC_LoadFile("localization/loc_english.txt"); +} + +/* +================ +LOC_Shutdown +================ +*/ +void LOC_Shutdown(void) +{ + free(localization.indices); + free(localization.entries); + free(localization.text); +} + +/* +================ +LOC_GetRawString + +Returns localized string if available, or NULL otherwise +================ +*/ +const char* LOC_GetRawString (const char *key) +{ + unsigned pos, end; + + if (!localization.numindices || !key || !*key || *key != '$') + return NULL; + key++; + + pos = COM_HashString(key) % localization.numindices; + end = pos; + + do + { + unsigned idx = localization.indices[pos]; + locentry_t *entry; + if (!idx) + return NULL; + + entry = &localization.entries[idx - 1]; + if (!Q_strcmp(entry->key, key)) + return entry->value; + + ++pos; + if (pos == localization.numindices) + pos = 0; + } while (pos != end); + + return NULL; +} + +/* +================ +LOC_GetString + +Returns localized string if available, or input string otherwise +================ +*/ +const char* LOC_GetString (const char *key) +{ + const char* value = LOC_GetRawString(key); + return value ? value : key; +} + +/* +================ +LOC_ParseArg + +Returns argument index (>= 0) and advances the string if it starts with a placeholder ({} or {N}), +otherwise returns a negative value and leaves the pointer unchanged +================ +*/ +static int LOC_ParseArg (const char **pstr) +{ + int arg; + const char *str = *pstr; + + // opening brace + if (*str != '{') + return -1; + ++str; + + // optional index, defaulting to 0 + arg = 0; + while (q_isdigit(*str)) + arg = arg * 10 + *str++ - '0'; + + // closing brace + if (*str != '}') + return -1; + *pstr = ++str; + + return arg; +} + +/* +================ +LOC_HasPlaceholders +================ +*/ +qboolean LOC_HasPlaceholders (const char *str) +{ + if (!localization.numindices) + return false; + while (*str) + { + if (LOC_ParseArg(&str) >= 0) + return true; + str++; + } + return false; +} + +/* +================ +LOC_Format + +Replaces placeholders (of the form {} or {N}) with the corresponding arguments + +Returns number of written chars, excluding the NUL terminator +If len > 0, output is always NUL-terminated +================ +*/ +size_t LOC_Format (const char *format, const char* (*getarg_fn) (int idx, void* userdata), void* userdata, char* out, size_t len) +{ + size_t written = 0; + int numargs = 0; + + if (!len) + { + Con_DPrintf("LOC_Format: no output space\n"); + return 0; + } + --len; // reserve space for the terminator + + while (*format && written < len) + { + const char* insert; + size_t space_left; + size_t insert_len; + int argindex = LOC_ParseArg(&format); + + if (argindex < 0) + { + out[written++] = *format++; + continue; + } + + insert = getarg_fn(argindex, userdata); + space_left = len - written; + insert_len = Q_strlen(insert); + + if (insert_len > space_left) + { + Con_DPrintf("LOC_Format: overflow at argument #%d\n", numargs); + insert_len = space_left; + } + + Q_memcpy(out + written, insert, insert_len); + written += insert_len; + } + + if (*format) + Con_DPrintf("LOC_Format: overflow\n"); + + out[written] = 0; + + return written; +} diff --git a/Quake/common.hpp b/Quake/common.hpp new file mode 100644 index 0000000..1fb57b2 --- /dev/null +++ b/Quake/common.hpp @@ -0,0 +1,412 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _Q_COMMON_H +#define _Q_COMMON_H + +// comndef.h -- general definitions + +#if defined(_WIN32) +#ifdef _MSC_VER +# pragma warning(disable:4244) + /* 'argument' : conversion from 'type1' to 'type2', + possible loss of data */ +# pragma warning(disable:4305) + /* 'identifier' : truncation from 'type1' to 'type2' */ + /* in our case, truncation from 'double' to 'float' */ +# pragma warning(disable:4267) + /* 'var' : conversion from 'size_t' to 'type', + possible loss of data (/Wp64 warning) */ +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#undef min +#undef max + +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) +#define GENERIC_TYPES(x, separator) \ + x(int, i) separator \ + x(unsigned int, u) separator \ + x(long, l) separator \ + x(unsigned long, ul) separator \ + x(long long, ll) separator \ + x(unsigned long long, ull) separator \ + x(float, f) separator \ + x(double, d) + +#define COMMA , +#define NO_COMMA + +#define IMPL_GENERIC_FUNCS(type, suffix) \ +static inline type q_min_##suffix (type a, type b) { \ + return (a < b) ? a : b; \ +} \ +static inline type q_max_##suffix (type a, type b) { \ + return (a > b) ? a : b; \ +} \ +static inline type clamp_##suffix (type minval, type val, type maxval) { \ + return (val < minval) ? minval : ((val > maxval) ? maxval : val); \ +} + +GENERIC_TYPES (IMPL_GENERIC_FUNCS, NO_COMMA) + +#define SELECT_Q_MIN(type, suffix) type: q_min_##suffix +#define q_min(a, b) _Generic((a) + (b), GENERIC_TYPES (SELECT_Q_MIN, COMMA))(a, b) + +#define SELECT_Q_MAX(type, suffix) type: q_max_##suffix +#define q_max(a, b) _Generic((a) + (b), GENERIC_TYPES (SELECT_Q_MAX, COMMA))(a, b) + +#define SELECT_CLAMP(type, suffix) type: clamp_##suffix +#define CLAMP(minval, val, maxval) _Generic((minval) + (val) + (maxval), \ + GENERIC_TYPES (SELECT_CLAMP, COMMA))(minval, val, maxval) + +#elif defined(__GNUC__) +/* min and max macros with type checking -- based on tyrquake. */ +#define q_max(a,b) ({ \ + const __typeof(a) a_ = (a); \ + const __typeof(b) b_ = (b); \ + (void)(&a_ == &b_); \ + (a_ > b_) ? a_ : b_; \ +}) +#define q_min(a,b) ({ \ + const __typeof(a) a_ = (a); \ + const __typeof(b) b_ = (b); \ + (void)(&a_ == &b_); \ + (a_ < b_) ? a_ : b_; \ +}) +#define CLAMP(_minval, x, _maxval) ({ \ + const __typeof(x) x_ = (x); \ + const __typeof(_minval) valmin_ = (_minval);\ + const __typeof(_maxval) valmax_ = (_maxval);\ + (void)(&x_ == &valmin_); \ + (void)(&x_ == &valmax_); \ + (x_ < valmin_) ? valmin_ : \ + (x_ > valmax_) ? valmax_ : x_; \ +}) + +#else +#define q_min(a, b) (((a) < (b)) ? (a) : (b)) +#define q_max(a, b) (((a) > (b)) ? (a) : (b)) +#define CLAMP(_minval, x, _maxval) \ + ((x) < (_minval) ? (_minval) : \ + (x) > (_maxval) ? (_maxval) : (x)) +#endif + +typedef struct sizebuf_s +{ + qboolean allowoverflow; // if false, do a Sys_Error + qboolean overflowed; // set to true if the buffer size failed + byte *data; + int maxsize; + int cursize; +} sizebuf_t; + +void SZ_Alloc (sizebuf_t *buf, int startsize); +void SZ_Free (sizebuf_t *buf); +void SZ_Clear (sizebuf_t *buf); +void *SZ_GetSpace (sizebuf_t *buf, int length); +void SZ_Write (sizebuf_t *buf, const void *data, int length); +void SZ_Print (sizebuf_t *buf, const char *data); // strcats onto the sizebuf + +//============================================================================ + +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + + +void ClearLink (link_t *l); +void RemoveLink (link_t *l); +void InsertLinkBefore (link_t *l, link_t *before); +void InsertLinkAfter (link_t *l, link_t *after); + +// (type *)STRUCT_FROM_LINK(link_t *link, type, member) +// ent = STRUCT_FROM_LINK(link,entity_t,order) +// FIXME: remove this mess! +#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - offsetof(t,m))) + +//============================================================================ + +typedef struct vec_header_t { + size_t capacity; + size_t size; +} vec_header_t; + +#define VEC_HEADER(v) (((vec_header_t*)(v))[-1]) + +#define VEC_PUSH(v,n) do { Vec_Grow((void**)&(v), sizeof((v)[0]), 1); (v)[VEC_HEADER(v).size++] = (n); } while (0) +#define VEC_SIZE(v) ((v) ? VEC_HEADER(v).size : 0) +#define VEC_FREE(v) Vec_Free((void**)&(v)) +#define VEC_CLEAR(v) Vec_Clear((void**)&(v)) + +void Vec_Grow (void **pvec, size_t element_size, size_t count); +void Vec_Append (void **pvec, size_t element_size, const void *data, size_t count); +void Vec_Clear (void **pvec); +void Vec_Free (void **pvec); + +//============================================================================ + +extern qboolean host_bigendian; + +extern short (*BigShort) (short l); +extern short (*LittleShort) (short l); +extern int (*BigLong) (int l); +extern int (*LittleLong) (int l); +extern float (*BigFloat) (float l); +extern float (*LittleFloat) (float l); + +//============================================================================ + +void MSG_WriteChar (sizebuf_t *sb, int c); +void MSG_WriteByte (sizebuf_t *sb, int c); +void MSG_WriteShort (sizebuf_t *sb, int c); +void MSG_WriteLong (sizebuf_t *sb, int c); +void MSG_WriteFloat (sizebuf_t *sb, float f); +void MSG_WriteString (sizebuf_t *sb, const char *s); +void MSG_WriteCoord (sizebuf_t *sb, float f, unsigned int flags); +void MSG_WriteAngle (sizebuf_t *sb, float f, unsigned int flags); +void MSG_WriteAngle16 (sizebuf_t *sb, float f, unsigned int flags); //johnfitz + +extern int msg_readcount; +extern qboolean msg_badread; // set if a read goes beyond end of message + +void MSG_BeginReading (void); +int MSG_ReadChar (void); +int MSG_ReadByte (void); +int MSG_ReadShort (void); +int MSG_ReadLong (void); +float MSG_ReadFloat (void); +const char *MSG_ReadString (void); + +float MSG_ReadCoord (unsigned int flags); +float MSG_ReadAngle (unsigned int flags); +float MSG_ReadAngle16 (unsigned int flags); //johnfitz + +//============================================================================ + +void Q_memset (void *dest, int fill, size_t count); +void Q_memcpy (void *dest, const void *src, size_t count); +int Q_memcmp (const void *m1, const void *m2, size_t count); +void Q_strcpy (char *dest, const char *src); +void Q_strncpy (char *dest, const char *src, int count); +int Q_strlen (const char *str); +char *Q_strrchr (const char *s, char c); +void Q_strcat (char *dest, const char *src); +int Q_strcmp (const char *s1, const char *s2); +int Q_strncmp (const char *s1, const char *s2, int count); +int Q_atoi (const char *str); +float Q_atof (const char *str); + + +#include "strl_fn.hpp" + +/* locale-insensitive strcasecmp replacement functions: */ +extern int q_strcasecmp (const char * s1, const char * s2); +extern int q_strncasecmp (const char *s1, const char *s2, size_t n); + +/* locale-insensitive case-insensitive alternative to strstr */ +extern char *q_strcasestr(const char *haystack, const char *needle); + +/* locale-insensitive strlwr/upr replacement functions: */ +extern char *q_strlwr (char *str); +extern char *q_strupr (char *str); + +/* snprintf, vsnprintf : always use our versions. */ +extern int q_snprintf (char *str, size_t size, const char *format, ...) FUNC_PRINTF(3,4); +extern int q_vsnprintf(char *str, size_t size, const char *format, va_list args) FUNC_PRINTF(3,0); + +//============================================================================ + +extern char com_token[1024]; +extern qboolean com_eof; + +typedef enum +{ + CPE_NOTRUNC, // return parse error in case of overflow + CPE_ALLOWTRUNC // truncate com_token in case of overflow +} cpe_mode; + +const char *COM_Parse (const char *data); +const char *COM_ParseEx (const char *data, cpe_mode mode); + + +extern int com_argc; +extern char **com_argv; + +extern int safemode; +/* safe mode: in true, the engine will behave as if one + of these arguments were actually on the command line: + -nosound, -nocdaudio, -nomidi, -stdvid, -dibonly, + -nomouse, -nojoy, -nolan + */ + +int COM_CheckParm (const char *parm); + +void COM_Init (void); +void COM_InitArgv (int argc, char **argv); +void COM_InitFilesystem (void); + +const char *COM_SkipPath (const char *pathname); +void COM_StripExtension (const char *in, char *out, size_t outsize); +void COM_FileBase (const char *in, char *out, size_t outsize); +void COM_AddExtension (char *path, const char *extension, size_t len); +#if 0 /* COM_DefaultExtension can be dangerous */ +void COM_DefaultExtension (char *path, const char *extension, size_t len); +#endif +const char *COM_FileGetExtension (const char *in); /* doesn't return NULL */ +void COM_ExtractExtension (const char *in, char *out, size_t outsize); +void COM_CreatePath (char *path); + +char *va (const char *format, ...) FUNC_PRINTF(1,2); +// does a varargs printf into a temp buffer + +unsigned COM_HashString (const char *str); + +// localization support for 2021 rerelease version: +void LOC_Init (void); +void LOC_Shutdown (void); +const char* LOC_GetRawString (const char *key); +const char* LOC_GetString (const char *key); +qboolean LOC_HasPlaceholders (const char *str); +size_t LOC_Format (const char *format, const char* (*getarg_fn)(int idx, void* userdata), void* userdata, char* out, size_t len); + +//============================================================================ + +// QUAKEFS +typedef struct +{ + char name[MAX_QPATH]; + int filepos, filelen; +} packfile_t; + +typedef struct pack_s +{ + char filename[MAX_OSPATH]; + int handle; + int numfiles; + packfile_t *files; +} pack_t; + +typedef struct searchpath_s +{ + unsigned int path_id; // identifier assigned to the game directory + // Note that /game1 and + // /game1 have the same id. + char filename[MAX_OSPATH]; + pack_t *pack; // only one of filename / pack will be used + struct searchpath_s *next; +} searchpath_t; + +extern searchpath_t *com_searchpaths; +extern searchpath_t *com_base_searchpaths; + +extern int com_filesize; +struct cache_user_s; + +extern char com_basedir[MAX_OSPATH]; +extern char com_gamedir[MAX_OSPATH]; +extern int file_from_pak; // global indicating that file came from a pak + +void COM_WriteFile (const char *filename, const void *data, int len); +int COM_OpenFile (const char *filename, int *handle, unsigned int *path_id); +int COM_FOpenFile (const char *filename, FILE **file, unsigned int *path_id); +qboolean COM_FileExists (const char *filename, unsigned int *path_id); +void COM_CloseFile (int h); + +// these procedures open a file using COM_FindFile and loads it into a proper +// buffer. the buffer is allocated with a total size of com_filesize + 1. the +// procedures differ by their buffer allocation method. +byte *COM_LoadStackFile (const char *path, void *buffer, int bufsize, + unsigned int *path_id); + // uses the specified stack stack buffer with the specified size + // of bufsize. if bufsize is too short, uses temp hunk. the bufsize + // must include the +1 +byte *COM_LoadTempFile (const char *path, unsigned int *path_id); + // allocates the buffer on the temp hunk. +byte *COM_LoadHunkFile (const char *path, unsigned int *path_id); + // allocates the buffer on the hunk. +byte *COM_LoadZoneFile (const char *path, unsigned int *path_id); + // allocates the buffer on the zone. +void COM_LoadCacheFile (const char *path, struct cache_user_s *cu, + unsigned int *path_id); + // uses cache mem for allocating the buffer. +byte *COM_LoadMallocFile (const char *path, unsigned int *path_id); + // allocates the buffer on the system mem (malloc). + +// Opens the given path directly, ignoring search paths. +// Returns NULL on failure, or else a '\0'-terminated malloc'ed buffer. +// Loads in "t" mode so CRLF to LF translation is performed on Windows. +byte *COM_LoadMallocFile_TextMode_OSPath (const char *path, long *len_out); + +// Attempts to parse an int, followed by a newline. +// Returns advanced buffer position. +// Doesn't signal parsing failure, but this is not needed for savegame loading. +const char *COM_ParseIntNewline(const char *buffer, int *value); + +// Attempts to parse a float followed by a newline. +// Returns advanced buffer position. +const char *COM_ParseFloatNewline(const char *buffer, float *value); + +// Parse a string of non-whitespace into com_token, then tries to consume a +// newline. Returns advanced buffer position. +const char *COM_ParseStringNewline(const char *buffer); + + +#define FS_ENT_NONE (0) +#define FS_ENT_FILE (1 << 0) +#define FS_ENT_DIRECTORY (1 << 1) + +/* The following FS_*() stdio replacements are necessary if one is + * to perform non-sequential reads on files reopened on pak files + * because we need the bookkeeping about file start/end positions. + * Allocating and filling in the fshandle_t structure is the users' + * responsibility when the file is initially opened. */ + +typedef struct _fshandle_t +{ + FILE *file; + qboolean pak; /* is the file read from a pak */ + long start; /* file or data start position */ + long length; /* file or data size */ + long pos; /* current position relative to start */ +} fshandle_t; + +size_t FS_fread(void *ptr, size_t size, size_t nmemb, fshandle_t *fh); +int FS_fseek(fshandle_t *fh, long offset, int whence); +long FS_ftell(fshandle_t *fh); +void FS_rewind(fshandle_t *fh); +int FS_feof(fshandle_t *fh); +int FS_ferror(fshandle_t *fh); +int FS_fclose(fshandle_t *fh); +int FS_fgetc(fshandle_t *fh); +char *FS_fgets(char *s, int size, fshandle_t *fh); +long FS_filelength (fshandle_t *fh); + + +extern struct cvar_s registered; +extern qboolean standard_quake, rogue, hipnotic; +extern qboolean fitzmode; + /* if true, run in fitzquake mode disabling custom quakespasm hacks */ + +#endif /* _Q_COMMON_H */ + diff --git a/Quake/console.cpp b/Quake/console.cpp new file mode 100644 index 0000000..8f5960b --- /dev/null +++ b/Quake/console.cpp @@ -0,0 +1,1315 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// console.c + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif +#include "quakedef.hpp" + +int con_linewidth; + +float con_cursorspeed = 4; + +#define CON_TEXTSIZE (1024 * 1024) //ericw -- was 65536. johnfitz -- new default size +#define CON_MINSIZE 16384 //johnfitz -- old default, now the minimum size + +int con_buffersize; //johnfitz -- user can now override default + +qboolean con_forcedup; // because no entities to refresh + +int con_totallines; // total lines in console scrollback +int con_backscroll; // lines up from bottom to display +int con_current; // where next message will be printed +int con_x; // offset in current line for next print +char *con_text = NULL; + +cvar_t con_notifytime = {"con_notifytime","3",CVAR_NONE}; //seconds +cvar_t con_logcenterprint = {"con_logcenterprint", "1", CVAR_NONE}; //johnfitz + +char con_lastcenterstring[1024]; //johnfitz + +#define NUM_CON_TIMES 4 +float con_times[NUM_CON_TIMES]; // realtime time the line was generated + // for transparent notify lines + +int con_vislines; + +qboolean con_debuglog = false; + +qboolean con_initialized; + + +/* +================ +Con_Quakebar -- johnfitz -- returns a bar of the desired length, but never wider than the console + +includes a newline, unless len >= con_linewidth. +================ +*/ +const char *Con_Quakebar (int len) +{ + static char bar[42]; + int i; + + len = q_min(len, (int)sizeof(bar) - 2); + len = q_min(len, con_linewidth); + + bar[0] = '\35'; + for (i = 1; i < len - 1; i++) + bar[i] = '\36'; + bar[len-1] = '\37'; + + if (len < con_linewidth) + { + bar[len] = '\n'; + bar[len+1] = 0; + } + else + bar[len] = 0; + + return bar; +} + +/* +================ +Con_ToggleConsole_f +================ +*/ +extern int history_line; //johnfitz + +void Con_ToggleConsole_f (void) +{ + if (key_dest == key_console/* || (key_dest == key_game && con_forcedup)*/) + { + key_lines[edit_line][1] = 0; // clear any typing + key_linepos = 1; + con_backscroll = 0; //johnfitz -- toggleconsole should return you to the bottom of the scrollback + history_line = edit_line; //johnfitz -- it should also return you to the bottom of the command history + + if (cls.state == ca_connected) + { + IN_Activate(); + key_dest = key_game; + } + else + { + M_Menu_Main_f (); + } + } + else + { + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + } + + SCR_EndLoadingPlaque (); + memset (con_times, 0, sizeof(con_times)); +} + +/* +================ +Con_Clear_f +================ +*/ +static void Con_Clear_f (void) +{ + if (con_text) + Q_memset (con_text, ' ', con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE + con_backscroll = 0; //johnfitz -- if console is empty, being scrolled up is confusing +} + +/* +================ +Con_Dump_f -- johnfitz -- adapted from quake2 source +================ +*/ +static void Con_Dump_f (void) +{ + int l, x; + const char *line; + FILE *f; + char buffer[1024]; + char name[MAX_OSPATH]; + + q_snprintf (name, sizeof(name), "%s/condump.txt", com_gamedir); + COM_CreatePath (name); + f = fopen (name, "w"); + if (!f) + { + Con_Printf ("ERROR: couldn't open file %s.\n", name); + return; + } + + // skip initial 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 < con_linewidth; x++) + if (line[x] != ' ') + break; + if (x != con_linewidth) + break; + } + + // write the remaining lines + buffer[con_linewidth] = 0; + for ( ; l <= con_current; l++) + { + line = con_text + (l%con_totallines)*con_linewidth; + strncpy (buffer, line, con_linewidth); + for (x = con_linewidth - 1; x >= 0; x--) + { + if (buffer[x] == ' ') + buffer[x] = 0; + else + break; + } + for (x = 0; buffer[x]; x++) + buffer[x] &= 0x7f; + + fprintf (f, "%s\n", buffer); + } + + fclose (f); + Con_Printf ("Dumped console text to %s.\n", name); +} + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify (void) +{ + int i; + + for (i = 0; i < NUM_CON_TIMES; i++) + con_times[i] = 0; +} + + +/* +================ +Con_MessageMode_f +================ +*/ +static void Con_MessageMode_f (void) +{ + if (cls.state != ca_connected || cls.demoplayback) + return; + chat_team = false; + key_dest = key_message; +} + +/* +================ +Con_MessageMode2_f +================ +*/ +static void Con_MessageMode2_f (void) +{ + if (cls.state != ca_connected || cls.demoplayback) + return; + chat_team = true; + key_dest = key_message; +} + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + char *tbuf; //johnfitz -- tbuf no longer a static array + int mark; //johnfitz + + width = (vid.conwidth >> 3) - 2; //johnfitz -- use vid.conwidth instead of vid.width + + if (width == con_linewidth) + return; + + oldwidth = con_linewidth; + con_linewidth = width; + oldtotallines = con_totallines; + con_totallines = con_buffersize / con_linewidth; //johnfitz -- con_buffersize replaces CON_TEXTSIZE + numlines = oldtotallines; + + if (con_totallines < numlines) + numlines = con_totallines; + + numchars = oldwidth; + + if (con_linewidth < numchars) + numchars = con_linewidth; + + mark = Hunk_LowMark (); //johnfitz + tbuf = (char *) Hunk_Alloc (con_buffersize); //johnfitz + + Q_memcpy (tbuf, con_text, con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE + Q_memset (con_text, ' ', con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE + + for (i = 0; i < numlines; i++) + { + for (j = 0; j < numchars; j++) + { + con_text[(con_totallines - 1 - i) * con_linewidth + j] = + tbuf[((con_current - i + oldtotallines) % oldtotallines) * oldwidth + j]; + } + } + + Hunk_FreeToLowMark (mark); //johnfitz + + Con_ClearNotify (); + + con_backscroll = 0; + con_current = con_totallines - 1; +} + + +/* +================ +Con_Init +================ +*/ +void Con_Init (void) +{ + int i; + + //johnfitz -- user settable console buffer size + i = COM_CheckParm("-consize"); + if (i && i < com_argc-1) { + con_buffersize = Q_atoi(com_argv[i+1])*1024; + if (con_buffersize < CON_MINSIZE) + con_buffersize = CON_MINSIZE; + } + else + con_buffersize = CON_TEXTSIZE; + //johnfitz + + con_text = (char *) Hunk_AllocName (con_buffersize, "context");//johnfitz -- con_buffersize replaces CON_TEXTSIZE + Q_memset (con_text, ' ', con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE + con_linewidth = -1; + + //johnfitz -- no need to run Con_CheckResize here + con_linewidth = 38; + con_totallines = con_buffersize / con_linewidth;//johnfitz -- con_buffersize replaces CON_TEXTSIZE + con_backscroll = 0; + con_current = con_totallines - 1; + //johnfitz + + Con_Printf ("Console initialized.\n"); + + Cvar_RegisterVariable (&con_notifytime); + Cvar_RegisterVariable (&con_logcenterprint); //johnfitz + + Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f); + Cmd_AddCommand ("messagemode", Con_MessageMode_f); + Cmd_AddCommand ("messagemode2", Con_MessageMode2_f); + Cmd_AddCommand ("clear", Con_Clear_f); + Cmd_AddCommand ("condump", Con_Dump_f); //johnfitz + con_initialized = true; +} + + +/* +=============== +Con_Linefeed +=============== +*/ +static void Con_Linefeed (void) +{ + //johnfitz -- improved scrolling + if (con_backscroll) + con_backscroll++; + if (con_backscroll > con_totallines - (glheight>>3) - 1) + con_backscroll = con_totallines - (glheight>>3) - 1; + //johnfitz + + con_x = 0; + con_current++; + Q_memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth); +} + +/* +================ +Con_Print + +Handles cursor positioning, line wrapping, etc +All console printing must go through this in order to be logged to disk +If no console is visible, the notify window will pop up. +================ +*/ +static void Con_Print (const char *txt) +{ + int y; + int c, l; + static int cr; + int mask; + qboolean boundary; + + //con_backscroll = 0; //johnfitz -- better console scrolling + + if (txt[0] == 1) + { + mask = 128; // go to colored text + S_LocalSound ("misc/talk.wav"); // play talk wav + txt++; + } + else if (txt[0] == 2) + { + mask = 128; // go to colored text + txt++; + } + else + mask = 0; + + boundary = true; + + while ( (c = *txt) ) + { + if (c <= ' ') + { + boundary = true; + } + else if (boundary) + { + // 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_x = 0; + + boundary = false; + } + + txt++; + + if (cr) + { + con_current--; + cr = false; + } + + if (!con_x) + { + Con_Linefeed (); + // mark time for transparent overlay + if (con_current >= 0) + con_times[con_current % NUM_CON_TIMES] = realtime; + } + + switch (c) + { + case '\n': + con_x = 0; + break; + + case '\r': + con_x = 0; + cr = 1; + break; + + default: // display character and advance + y = con_current % con_totallines; + con_text[y*con_linewidth+con_x] = c | mask; + con_x++; + if (con_x >= con_linewidth) + con_x = 0; + break; + } + } +} + + +// borrowed from uhexen2 by S.A. for new procs, LOG_Init, LOG_Close + +static char logfilename[MAX_OSPATH]; // current logfile name +static int log_fd = -1; // log file descriptor + +/* +================ +Con_DebugLog +================ +*/ +void Con_DebugLog(const char *msg) +{ + if (log_fd == -1) + return; + + if (write(log_fd, msg, strlen(msg)) < 0) + { + close (log_fd); + log_fd = -1; + con_debuglog = false; + fprintf (stderr, "Error writing to log file\n"); + } +} + + +/* +================ +Con_Printf + +Handles cursor positioning, line wrapping, etc +================ +*/ +#define MAXPRINTMSG 4096 +void Con_Printf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + static qboolean inupdate; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + +// also echo to debugging console + Sys_Printf ("%s", msg); + +// log all messages to file + if (con_debuglog) + Con_DebugLog(msg); + + if (!con_initialized) + return; + + if (cls.state == ca_dedicated) + return; // no graphics mode + +// write it to the scrollable buffer + Con_Print (msg); + +// update the screen if the console is displayed + if (cls.signon != SIGNONS && !scr_disabled_for_loading ) + { + // protect against infinite loop if something in SCR_UpdateScreen calls + // Con_Printd + if (!inupdate) + { + inupdate = true; + SCR_UpdateScreen (); + inupdate = false; + } + } +} + +/* +================ +Con_DWarning -- ericw + +same as Con_Warning, but only prints if "developer" cvar is set. +use for "exceeds standard limit of" messages, which are only relevant for developers +targetting vanilla engines +================ +*/ +void Con_DWarning (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if (!developer.value) + return; // don't confuse non-developers with techie stuff... + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Con_SafePrintf ("\x02Warning: "); + Con_Printf ("%s", msg); +} + +/* +================ +Con_Warning -- johnfitz -- prints a warning to the console +================ +*/ +void Con_Warning (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Con_SafePrintf ("\x02Warning: "); + Con_Printf ("%s", msg); +} + +/* +================ +Con_DPrintf + +A Con_Printf that only shows up if the "developer" cvar is set +================ +*/ +void Con_DPrintf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if (!developer.value) + return; // don't confuse non-developers with techie stuff... + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + Con_SafePrintf ("%s", msg); //johnfitz -- was Con_Printf +} + +/* +================ +Con_DPrintf2 -- johnfitz -- only prints if "developer" >= 2 + +currently not used +================ +*/ +void Con_DPrintf2 (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if (developer.value >= 2) + { + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + Con_Printf ("%s", msg); + } +} + + +/* +================== +Con_SafePrintf + +Okay to call even when the screen can't be updated +================== +*/ +void Con_SafePrintf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + int temp; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + temp = scr_disabled_for_loading; + scr_disabled_for_loading = true; + Con_Printf ("%s", msg); + scr_disabled_for_loading = temp; +} + +/* +================ +Con_CenterPrintf -- johnfitz -- pad each line with spaces to make it appear centered +================ +*/ +void Con_CenterPrintf (int linewidth, const char *fmt, ...) FUNC_PRINTF(2,3); +void Con_CenterPrintf (int linewidth, const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; //the original message + char line[MAXPRINTMSG]; //one line from the message + char spaces[21]; //buffer for spaces + char *src, *dst; + int len, s; + + va_start (argptr, fmt); + q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + linewidth = q_min(linewidth, con_linewidth); + for (src = msg; *src; ) + { + dst = line; + while (*src && *src != '\n') + *dst++ = *src++; + *dst = 0; + if (*src == '\n') + src++; + + len = strlen(line); + if (len < linewidth) + { + s = (linewidth-len)/2; + memset (spaces, ' ', s); + spaces[s] = 0; + Con_Printf ("%s%s\n", spaces, line); + } + else + Con_Printf ("%s\n", line); + } +} + +/* +================== +Con_LogCenterPrint -- johnfitz -- echo centerprint message to the console +================== +*/ +void Con_LogCenterPrint (const char *str) +{ + if (!strcmp(str, con_lastcenterstring)) + return; //ignore duplicates + + if (cl.gametype == GAME_DEATHMATCH && con_logcenterprint.value != 2) + return; //don't log in deathmatch + + strcpy(con_lastcenterstring, str); + + if (con_logcenterprint.value) + { + Con_Printf ("%s", Con_Quakebar(40)); + Con_CenterPrintf (40, "%s\n", str); + Con_Printf ("%s", Con_Quakebar(40)); + Con_ClearNotify (); + } +} + +/* +============================================================================== + + TAB COMPLETION + +============================================================================== +*/ + +//johnfitz -- tab completion stuff +//unique defs +char key_tabpartial[MAXCMDLINE]; +typedef struct tab_s +{ + const char *name; + const char *type; + struct tab_s *next; + struct tab_s *prev; +} tab_t; +tab_t *tablist; + +//defs from elsewhere +extern qboolean keydown[256]; +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + const char *name; + xcommand_t function; +} cmd_function_t; +extern cmd_function_t *cmd_functions; +#define MAX_ALIAS_NAME 32 +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; +} cmdalias_t; +extern cmdalias_t *cmd_alias; + +/* +============ +AddToTabList -- johnfitz + +tablist is a doubly-linked loop, alphabetized by name +============ +*/ + +// bash_partial is the string that can be expanded, +// aka Linux Bash shell. -- S.A. +static char bash_partial[80]; +static qboolean bash_singlematch; + +void AddToTabList (const char *name, const char *type) +{ + tab_t *t,*insert; + char *i_bash; + const char *i_name; + + if (!*bash_partial) + { + strncpy (bash_partial, name, 79); + bash_partial[79] = '\0'; + } + else + { + bash_singlematch = 0; + // find max common between bash_partial and name + i_bash = bash_partial; + i_name = name; + while (*i_bash && (*i_bash == *i_name)) + { + i_bash++; + i_name++; + } + *i_bash = 0; + } + + t = (tab_t *) Hunk_Alloc(sizeof(tab_t)); + t->name = name; + t->type = type; + + if (!tablist) //create list + { + tablist = t; + t->next = t; + t->prev = t; + } + else if (strcmp(name, tablist->name) < 0) //insert at front + { + t->next = tablist; + t->prev = tablist->prev; + t->next->prev = t; + t->prev->next = t; + tablist = t; + } + else //insert later + { + insert = tablist; + do + { + if (strcmp(name, insert->name) < 0) + break; + insert = insert->next; + } while (insert != tablist); + + t->next = insert; + t->prev = insert->prev; + t->next->prev = t; + t->prev->next = t; + } +} + +typedef struct arg_completion_type_s +{ + const char *command; + filelist_item_t **filelist; +} arg_completion_type_t; + +static const arg_completion_type_t arg_completion_types[] = +{ + { "map ", &extralevels }, + { "changelevel ", &extralevels }, + { "game ", &modlist }, + { "record ", &demolist }, + { "playdemo ", &demolist }, + { "timedemo ", &demolist } +}; + +static const int num_arg_completion_types = Q_COUNTOF(arg_completion_types); + +/* +============ +FindCompletion -- stevenaaus +============ +*/ +const char *FindCompletion (const char *partial, filelist_item_t *filelist, int *nummatches_out) +{ + static char matched[40]; + char *i_matched, *i_name; + filelist_item_t *file; + int init, match, plen; + + memset(matched, 0, sizeof(matched)); + plen = strlen(partial); + match = 0; + + for (file = filelist, init = 0; file; file = file->next) + { + if (!strncmp(file->name, partial, plen)) + { + if (init == 0) + { + init = 1; + strncpy (matched, file->name, sizeof(matched)-1); + matched[sizeof(matched)-1] = '\0'; + } + else + { // find max common + i_matched = matched; + i_name = file->name; + while (*i_matched && (*i_matched == *i_name)) + { + i_matched++; + i_name++; + } + *i_matched = 0; + } + match++; + } + } + + *nummatches_out = match; + + if (match > 1) + { + for (file = filelist; file; file = file->next) + { + if (!strncmp(file->name, partial, plen)) + Con_SafePrintf (" %s\n", file->name); + } + Con_SafePrintf ("\n"); + } + + return matched; +} + +/* +============ +BuildTabList -- johnfitz +============ +*/ +void BuildTabList (const char *partial) +{ + cmdalias_t *alias; + cmd_function_t *cmd; + int len; + + tablist = NULL; + len = strlen(partial); + + bash_partial[0] = 0; + bash_singlematch = 1; + + for (auto cvar : std::ranges::views::values(CLIENT_VARIABLES)) + if (!Q_strncmp (partial, cvar->name, len)) + AddToTabList (cvar->name, "cvar"); + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) + if (!Q_strncmp (partial,cmd->name, len)) + AddToTabList (cmd->name, "command"); + + for (alias=cmd_alias ; alias ; alias=alias->next) + if (!Q_strncmp (partial, alias->name, len)) + AddToTabList (alias->name, "alias"); +} + +/* +============ +Con_TabComplete -- johnfitz +============ +*/ +void Con_TabComplete (void) +{ + char partial[MAXCMDLINE]; + const char *match; + static char *c; + tab_t *t; + int mark, i, j; + +// if editline is empty, return + if (key_lines[edit_line][1] == 0) + return; + +// get partial string (space -> cursor) + if (!key_tabpartial[0]) //first time through, find new insert point. (Otherwise, use previous.) + { + //work back from cursor until you find a space, quote, semicolon, or prompt + c = key_lines[edit_line] + key_linepos - 1; //start one space left of cursor + while (*c!=' ' && *c!='\"' && *c!=';' && c!=key_lines[edit_line]) + c--; + c++; //start 1 char after the separator we just found + } + for (i = 0; c + i < key_lines[edit_line] + key_linepos; i++) + partial[i] = c[i]; + partial[i] = 0; + +// Map autocomplete function -- S.A +// Since we don't have argument completion, this hack will do for now... + for (j=0; j= MAXCMDLINE) + key_linepos = MAXCMDLINE - 1; + // if only one match, append a space + if (key_linepos < MAXCMDLINE - 1 && + key_lines[edit_line][key_linepos] == 0 && (nummatches == 1)) + { + key_lines[edit_line][key_linepos] = ' '; + key_linepos++; + key_lines[edit_line][key_linepos] = 0; + } + c = key_lines[edit_line] + key_linepos; + return; + } + } + +//if partial is empty, return + if (partial[0] == 0) + return; + +//trim trailing space becuase it screws up string comparisons + if (i > 0 && partial[i-1] == ' ') + partial[i-1] = 0; + +// find a match + mark = Hunk_LowMark(); + if (!key_tabpartial[0]) //first time through + { + q_strlcpy (key_tabpartial, partial, MAXCMDLINE); + BuildTabList (key_tabpartial); + + if (!tablist) + return; + + // print list if length > 1 + if (tablist->next != tablist) + { + t = tablist; + Con_SafePrintf("\n"); + do + { + Con_SafePrintf(" %s (%s)\n", t->name, t->type); + t = t->next; + } while (t != tablist); + Con_SafePrintf("\n"); + } + + // match = tablist->name; + // First time, just show maximum matching chars -- S.A. + match = bash_partial; + } + else + { + BuildTabList (key_tabpartial); + + if (!tablist) + return; + + //find current match -- can't save a pointer because the list will be rebuilt each time + t = tablist; + match = keydown[K_SHIFT] ? t->prev->name : t->name; + do + { + if (!Q_strcmp(t->name, partial)) + { + match = keydown[K_SHIFT] ? t->prev->name : t->next->name; + break; + } + t = t->next; + } while (t != tablist); + } + Hunk_FreeToLowMark(mark); //it's okay to free it here because match is a pointer to persistent data + +// insert new match into edit line + q_strlcpy (partial, match, MAXCMDLINE); //first copy match string + q_strlcat (partial, key_lines[edit_line] + key_linepos, MAXCMDLINE); //then add chars after cursor + *c = '\0'; //now copy all of this into edit line + q_strlcat (key_lines[edit_line], partial, MAXCMDLINE); + key_linepos = c - key_lines[edit_line] + Q_strlen(match); //set new cursor position + if (key_linepos >= MAXCMDLINE) + key_linepos = MAXCMDLINE - 1; + +// if cursor is at end of string, let's append a space to make life easier + if (key_linepos < MAXCMDLINE - 1 && + key_lines[edit_line][key_linepos] == 0 && bash_singlematch) + { + key_lines[edit_line][key_linepos] = ' '; + key_linepos++; + key_lines[edit_line][key_linepos] = 0; + // S.A.: the map argument completion (may be in combination with the bash-style + // display behavior changes, causes weirdness when completing the arguments for + // the changelevel command. the line below "fixes" it, although I'm not sure about + // the reason, yet, neither do I know any possible side effects of it: + c = key_lines[edit_line] + key_linepos; + } +} + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + int i, x, v; + const char *text; + float time; + + GL_SetCanvas (CANVAS_CONSOLE); //johnfitz + v = vid.conheight; //johnfitz + + for (i = con_current-NUM_CON_TIMES+1; i <= con_current; i++) + { + if (i < 0) + continue; + time = con_times[i % NUM_CON_TIMES]; + if (time == 0) + continue; + time = realtime - time; + if (time > con_notifytime.value) + continue; + text = con_text + (i % con_totallines)*con_linewidth; + + clearnotify = 0; + + for (x = 0; x < con_linewidth; x++) + Draw_Character ((x+1)<<3, v, text[x]); + + v += 8; + + scr_tileclear_updates = 0; //johnfitz + } + + if (key_dest == key_message) + { + clearnotify = 0; + + if (chat_team) + { + Draw_String (8, v, "say_team:"); + x = 11; + } + else + { + Draw_String (8, v, "say:"); + x = 6; + } + + text = Key_GetChatBuffer(); + i = Key_GetChatMsgLen(); + if (i > con_linewidth - x - 1) + text += i - con_linewidth + x + 1; + + while (*text) + { + Draw_Character (x<<3, v, *text); + x++; + text++; + } + + Draw_Character (x<<3, v, 10 + ((int)(realtime*con_cursorspeed)&1)); + v += 8; + + scr_tileclear_updates = 0; //johnfitz + } +} + +/* +================ +Con_DrawInput -- johnfitz -- modified to allow insert editing + +The input line scrolls horizontally if typing goes beyond the right edge +================ +*/ +extern qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling + +void Con_DrawInput (void) +{ + int i, ofs; + + if (key_dest != key_console && !con_forcedup) + return; // don't draw anything + +// prestep if horizontally scrolling + if (key_linepos >= con_linewidth) + ofs = 1 + key_linepos - con_linewidth; + else + ofs = 0; + +// draw input string + for (i = 0; key_lines[edit_line][i+ofs] && i < con_linewidth; i++) + Draw_Character ((i+1)<<3, vid.conheight - 16, key_lines[edit_line][i+ofs]); + +// johnfitz -- new cursor handling + if (!((int)((realtime-key_blinktime)*con_cursorspeed) & 1)) + { + i = key_linepos - ofs; + Draw_Pic ((i+1)<<3, vid.conheight - 16, key_insert ? pic_ins : pic_ovr); + } +} + +/* +================ +Con_DrawConsole -- johnfitz -- heavy revision + +Draws the console with the solid background +The typing input line at the bottom should only be drawn if typing is allowed +================ +*/ +void Con_DrawConsole (int lines, qboolean drawinput) +{ + int i, x, y, j, sb, rows; + const char *text; + char ver[32]; + + if (lines <= 0) + return; + + con_vislines = lines * vid.conheight / glheight; + GL_SetCanvas (CANVAS_CONSOLE); + +// draw the background + Draw_ConsoleBackground (); + +// draw the buffer text + rows = (con_vislines +7)/8; + y = vid.conheight - rows*8; + rows -= 2; //for input and version lines + sb = (con_backscroll) ? 2 : 0; + + for (i = con_current - rows + 1; i <= con_current - sb; i++, y += 8) + { + j = i - con_backscroll; + if (j < 0) + j = 0; + text = con_text + (j % con_totallines)*con_linewidth; + + for (x = 0; x < con_linewidth; x++) + Draw_Character ( (x + 1)<<3, y, text[x]); + } + +// draw scrollback arrows + if (con_backscroll) + { + y += 8; // blank line + for (x = 0; x < con_linewidth; x += 4) + Draw_Character ((x + 1)<<3, y, '^'); + y += 8; + } + +// draw the input prompt, user text, and cursor + if (drawinput) + Con_DrawInput (); + +//draw version number in bottom right + y += 8; + q_snprintf (ver, sizeof(ver), "QuakeSpasm " QUAKESPASM_VER_STRING); + for (x = 0; x < (int)strlen(ver); x++) + Draw_Character ((con_linewidth - strlen(ver) + x + 2)<<3, y, ver[x] /*+ 128*/); +} + + +/* +================== +Con_NotifyBox +================== +*/ +void Con_NotifyBox (const char *text) +{ + double t1, t2; + int lastkey, lastchar; + +// during startup for sound / cd warnings + Con_Printf ("\n\n%s", Con_Quakebar(40)); //johnfitz + Con_Printf ("%s", text); + Con_Printf ("Press a key.\n"); + Con_Printf ("%s", Con_Quakebar(40)); //johnfitz + + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + + Key_BeginInputGrab (); + do + { + t1 = Sys_DoubleTime (); + SCR_UpdateScreen (); + Sys_SendKeyEvents (); + Key_GetGrabbedInput (&lastkey, &lastchar); + Sys_Sleep (16); + t2 = Sys_DoubleTime (); + realtime += t2-t1; // make the cursor blink + } while (lastkey == -1 && lastchar == -1); + Key_EndInputGrab (); + + Con_Printf ("\n"); + IN_Activate(); + key_dest = key_game; + realtime = 0; // put the cursor back to invisible +} + + +void LOG_Init (quakeparms_t *parms) +{ + time_t inittime; + char session[24]; + + if (!COM_CheckParm("-condebug")) + return; + + inittime = time (NULL); + strftime (session, sizeof(session), "%m/%d/%Y %H:%M:%S", localtime(&inittime)); + q_snprintf (logfilename, sizeof(logfilename), "%s/qconsole.log", parms->basedir); + +// unlink (logfilename); + + log_fd = open (logfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (log_fd == -1) + { + fprintf (stderr, "Error: Unable to create log file %s\n", logfilename); + return; + } + + con_debuglog = true; + Con_DebugLog (va("LOG started on: %s \n", session)); + +} + +void LOG_Close (void) +{ + if (log_fd == -1) + return; + close (log_fd); + log_fd = -1; +} + diff --git a/Quake/console.hpp b/Quake/console.hpp new file mode 100644 index 0000000..0fa4a8c --- /dev/null +++ b/Quake/console.hpp @@ -0,0 +1,69 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __CONSOLE_H +#define __CONSOLE_H + +// +// console +// +extern int con_totallines; +extern int con_backscroll; +extern qboolean con_forcedup; // because no entities to refresh +extern qboolean con_initialized; +extern byte *con_chars; + +extern char con_lastcenterstring[]; //johnfitz + +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init (void); +void Con_DrawConsole (int lines, qboolean drawinput); +void Con_Printf (const char *fmt, ...) FUNC_PRINTF(1,2); +void Con_DWarning (const char *fmt, ...) FUNC_PRINTF(1,2); //ericw +void Con_Warning (const char *fmt, ...) FUNC_PRINTF(1,2); //johnfitz +void Con_DPrintf (const char *fmt, ...) FUNC_PRINTF(1,2); +void Con_DPrintf2 (const char *fmt, ...) FUNC_PRINTF(1,2); //johnfitz +void Con_SafePrintf (const char *fmt, ...) FUNC_PRINTF(1,2); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_ToggleConsole_f (void); + +void Con_NotifyBox (const char *text); // during startup for sound / cd warnings + +void Con_Show (void); +void Con_Hide (void); + +const char *Con_Quakebar (int len); +void Con_TabComplete (void); +void Con_LogCenterPrint (const char *str); + +// +// debuglog +// +void LOG_Init (quakeparms_t *parms); +void LOG_Close (void); +void Con_DebugLog (const char *msg); + +#endif /* __CONSOLE_H */ + diff --git a/Quake/crc.cpp b/Quake/crc.cpp new file mode 100644 index 0000000..b42074e --- /dev/null +++ b/Quake/crc.cpp @@ -0,0 +1,94 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* crc.c */ + +#include "quakedef.hpp" +#include "crc.hpp" + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +static const unsigned short crctable[256] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} + +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} + +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} + +//johnfitz -- texture crc +unsigned short CRC_Block (const byte *start, int count) +{ + unsigned short crc; + + CRC_Init (&crc); + while (count--) + crc = (crc << 8) ^ crctable[(crc >> 8) ^ *start++]; + + return crc; +} diff --git a/Quake/crc.hpp b/Quake/crc.hpp new file mode 100644 index 0000000..421f522 --- /dev/null +++ b/Quake/crc.hpp @@ -0,0 +1,33 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_CRC_H +#define _QUAKE_CRC_H + +/* crc.h */ + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); +unsigned short CRC_Block (const byte *start, int count); //johnfitz -- texture crc + +#endif /* _QUAKE_CRC_H */ + diff --git a/Quake/cvar.cpp b/Quake/cvar.cpp new file mode 100644 index 0000000..66a3136 --- /dev/null +++ b/Quake/cvar.cpp @@ -0,0 +1,560 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cvar.c -- dynamic variable tracking + +#include "quakedef.hpp" + +#include +#include +#include +#include +#include + +static cvar_t *cvar_vars; +static char cvar_null_string[] = ""; + +std::map CLIENT_VARIABLES{}; + +//============================================================================== +// +// USER COMMANDS +// +//============================================================================== + +void Cvar_Reset(const std::string &name); //johnfitz + +/* +============ +Cvar_List_f -- johnfitz +============ +*/ +void Cvar_List_f() { + cvar_t *cvar; + const char *partial; + int len, count; + + if (Cmd_Argc() > 1) { + partial = Cmd_Argv(1); + len = Q_strlen(partial); + } else { + partial = NULL; + len = 0; + } + + count = 0; + for (const auto &[name, var]: CLIENT_VARIABLES) { + if (partial && Q_strncmp(partial, name.c_str(), len)) { + continue; + } + Con_SafePrintf("%s%s %s \"%s\"\n", + (var->flags & CVAR_ARCHIVE) ? "*" : " ", + (var->flags & CVAR_NOTIFY) ? "s" : " ", + name.c_str(), + var->string); + count++; + } + + Con_SafePrintf("%i cvars", count); + if (partial) { + Con_SafePrintf(" beginning with \"%s\"", partial); + } + Con_SafePrintf("\n"); +} + +/* +============ +Cvar_Inc_f -- johnfitz +============ +*/ +void Cvar_Inc_f(void) { + switch (Cmd_Argc()) { + default: + case 1: + Con_Printf("inc [amount] : increment cvar\n"); + break; + case 2: + Cvar_SetValue(Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) + 1); + break; + case 3: + Cvar_SetValue(Cmd_Argv(1), Cvar_VariableValue(Cmd_Argv(1)) + Q_atof(Cmd_Argv(2))); + break; + } +} + +/* +============ +Cvar_Toggle_f -- johnfitz +============ +*/ +void Cvar_Toggle_f(void) { + switch (Cmd_Argc()) { + default: + case 1: + Con_Printf("toggle : toggle cvar\n"); + break; + case 2: + if (Cvar_VariableValue(Cmd_Argv(1))) + Cvar_Set(Cmd_Argv(1), "0"); + else + Cvar_Set(Cmd_Argv(1), "1"); + break; + } +} + +/* +============ +Cvar_Cycle_f -- johnfitz +============ +*/ +void Cvar_Cycle_f() { + int i; + + if (Cmd_Argc() < 3) { + Con_Printf("cycle : cycle cvar through a list of values\n"); + return; + } + + //loop through the args until you find one that matches the current cvar value. + //yes, this will get stuck on a list that contains the same value twice. + //it's not worth dealing with, and i'm not even sure it can be dealt with. + for (i = 2; i < Cmd_Argc(); i++) { + //zero is assumed to be a string, even though it could actually be zero. The worst case + //is that the first time you call this command, it won't match on zero when it should, but after that, + //it will be comparing strings that all had the same source (the user) so it will work. + if (Q_atof(Cmd_Argv(i)) == 0) { + if (!strcmp(Cmd_Argv(i), Cvar_VariableString(Cmd_Argv(1)))) + break; + } else { + if (Q_atof(Cmd_Argv(i)) == Cvar_VariableValue(Cmd_Argv(1))) + break; + } + } + + if (i == Cmd_Argc()) + Cvar_Set(Cmd_Argv(1), Cmd_Argv(2)); // no match + else if (i + 1 == Cmd_Argc()) + Cvar_Set(Cmd_Argv(1), Cmd_Argv(2)); // matched last value in list + else + Cvar_Set(Cmd_Argv(1), Cmd_Argv(i + 1)); // matched earlier in list +} + +/* +============ +Cvar_Reset_f -- johnfitz +============ +*/ +void Cvar_Reset_f() { + switch (Cmd_Argc()) { + default: + case 1: + Con_Printf("reset : reset cvar to default\n"); + break; + case 2: + Cvar_Reset(Cmd_Argv(1)); + break; + } +} + +/* +============ +Cvar_ResetAll_f -- johnfitz +============ +*/ +void Cvar_ResetAll_f(void) { + cvar_t *var; + + for (auto name: std::views::keys(CLIENT_VARIABLES)) + Cvar_Reset(name); +} + +/* +============ +Cvar_ResetCfg_f -- QuakeSpasm +============ +*/ +void Cvar_ResetCfg_f() { + for (const auto &var: std::views::values(CLIENT_VARIABLES)) + if (var->flags & CVAR_ARCHIVE) Cvar_Reset(var->name); +} + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +============ +Cvar_Init -- johnfitz +============ +*/ + +void Cvar_Init() { + Cmd_AddCommand("cvarlist", Cvar_List_f); + Cmd_AddCommand("toggle", Cvar_Toggle_f); + Cmd_AddCommand("cycle", Cvar_Cycle_f); + Cmd_AddCommand("inc", Cvar_Inc_f); + Cmd_AddCommand("reset", Cvar_Reset_f); + Cmd_AddCommand("resetall", Cvar_ResetAll_f); + Cmd_AddCommand("resetcfg", Cvar_ResetCfg_f); +} + +//============================================================================== +// +// CVAR FUNCTIONS +// +//============================================================================== + +/* +============ +Cvar_FindVar +============ +*/ +std::optional Cvar_FindVar(const std::string &desired) { + for (auto &[name, var]: CLIENT_VARIABLES) { + if (name == desired) + return var; + } + + return std::nullopt; +} + +cvar_t *Cvar_FindVarAfter(const char *prev_name, unsigned int with_flags) { + // cvar_t *var; + // + // if (*prev_name) + // { + // var = Cvar_FindVar (prev_name); + // if (!var) + // return NULL; + // var = var->next; + // } + // else + // var = cvar_vars; + // + // // search for the next cvar matching the needed flags + // while (var) + // { + // if ((var->flags & with_flags) || !with_flags) + // break; + // var = var->next; + // } + // return var; + return nullptr; // TODO +} + +/* +============ +Cvar_LockVar +============ +*/ +void Cvar_LockVar(const std::string &name) { + if (const auto var = Cvar_FindVar(name); var.has_value()) + var.value()->flags |= CVAR_LOCKED; +} + +void Cvar_UnlockVar(const std::string &name) { + if (const auto var = Cvar_FindVar(name); var.has_value()) + var.value()->flags &= ~CVAR_LOCKED; +} + +void Cvar_UnlockAll() { + for (const auto &var: std::views::values(CLIENT_VARIABLES)) { + var->flags &= ~CVAR_LOCKED; + } +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue(const char *var_name) { + const auto var = Cvar_FindVar(var_name); + if (!var.has_value()) + return 0; + return std::strtof(var.value()->string, nullptr); +} + + +/* +============ +Cvar_VariableString +============ +*/ +const char *Cvar_VariableString(const char *var_name) { + const auto var = Cvar_FindVar(var_name); + if (!var.has_value()) + return cvar_null_string; + return var.value()->string; +} + + +/* +============ +Cvar_CompleteVariable +============ +*/ +std::optional Cvar_CompleteVariable(const std::string_view &partial) { + if (!partial.empty()) + return std::nullopt; + + // check functions + for (const auto &name: CLIENT_VARIABLES | std::views::keys) { + if (name.contains(name)) + return name; + } + + return std::nullopt; +} + +/* +============ +Cvar_Reset -- johnfitz +============ +*/ +void Cvar_Reset(const std::string &name) { + if (const auto var = Cvar_FindVar(name); !var.has_value()) + Con_Printf("variable \"%s\" not found\n", name.c_str()); + else + Cvar_SetQuick(var.value(), var.value()->default_string); +} + +void Cvar_SetQuick(cvar_t *var, const std::string &value) { + if (var->flags & (CVAR_ROM | CVAR_LOCKED)) + return; + if (!(var->flags & CVAR_REGISTERED)) + return; + + if (!var->string) + var->string = Z_Strdup(value.c_str()); + else { + int len; + + if (!strcmp(var->string, value.c_str())) + return; // no change + + var->flags |= CVAR_CHANGED; + len = value.length(); + if (len != Q_strlen(var->string)) { + Z_Free((void *) var->string); + var->string = (char *) Z_Malloc(len + 1); + } + memcpy((char *) var->string, value.c_str(), len + 1); + } + + var->value = Q_atof(var->string); + + //johnfitz -- save initial value for "reset" command + if (!var->default_string) + var->default_string = Z_Strdup(var->string); + //johnfitz -- during initialization, update default too + else if (!host_initialized) { + // Sys_Printf("changing default of %s: %s -> %s\n", + // var->name, var->default_string, var->string); + Z_Free((void *) var->default_string); + var->default_string = Z_Strdup(var->string); + } + //johnfitz + + if (var->callback) + var->callback(var); +} + +void Cvar_SetValueQuick(cvar_t *var, const float value) { + char val[32], *ptr = val; + + if (value == (float) ((int) value)) + q_snprintf(val, sizeof(val), "%i", (int) value); + else { + q_snprintf(val, sizeof(val), "%f", value); + // kill trailing zeroes + while (*ptr) + ptr++; + while (--ptr > val && *ptr == '0' && ptr[-1] != '.') + *ptr = '\0'; + } + + Cvar_SetQuick(var, val); +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set(const std::string &name, const std::string &value) { + auto var = Cvar_FindVar(name); + if (!var.has_value()) { + // there is an error in C code if this happens + Con_Printf("Cvar_Set: variable %s not found\n", name.c_str()); + return; + } + + Cvar_SetQuick(var.value(), value); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue(const char *var_name, const float value) { + char val[32], *ptr = val; + + if (value == (float) ((int) value)) + q_snprintf(val, sizeof(val), "%i", (int) value); + else { + q_snprintf(val, sizeof(val), "%f", value); + // kill trailing zeroes + while (*ptr) + ptr++; + while (--ptr > val && *ptr == '0' && ptr[-1] != '.') + *ptr = '\0'; + } + + Cvar_Set(var_name, val); +} + +/* +============ +Cvar_SetROM +============ +*/ +void Cvar_SetROM(const char *var_name, const char *value) { + if (const auto var = Cvar_FindVar(var_name); var.has_value()) { + var.value()->flags &= ~CVAR_ROM; + Cvar_SetQuick(var.value(), value); + var.value()->flags |= CVAR_ROM; + } +} + +/* +============ +Cvar_SetValueROM +============ +*/ +void Cvar_SetValueROM(const char *var_name, const float value) { + if (const auto var = Cvar_FindVar(var_name); var.has_value()) { + var.value()->flags &= ~CVAR_ROM; + Cvar_SetValueQuick(var.value(), value); + var.value()->flags |= CVAR_ROM; + } +} + +/* +============ +Cvar_RegisterVariable + +Adds a freestanding variable to the variable list. +============ +*/ +void Cvar_RegisterVariable(cvar_t *variable) { + char value[512]; + qboolean set_rom; + cvar_t *cursor, *prev; //johnfitz -- sorted list insert + + // first check to see if it has already been defined + if (Cvar_FindVar(variable->name)) { + Con_Printf("Can't register variable %s, already defined\n", variable->name); + return; + } + + // check for overlap with a command + if (Cmd_Exists(variable->name)) { + Con_Printf("Cvar_RegisterVariable: %s is a command\n", variable->name); + return; + } + + CLIENT_VARIABLES.emplace(std::string(variable->name), variable); + + //johnfitz + variable->flags |= CVAR_REGISTERED; + + // copy the value off, because future sets will Z_Free it + std::strncpy(value, variable->string, sizeof(value)); + variable->string = NULL; + variable->default_string = NULL; + + if (!(variable->flags & CVAR_CALLBACK)) + variable->callback = NULL; + + // set it through the function to be consistent + set_rom = (variable->flags & CVAR_ROM); + variable->flags &= ~CVAR_ROM; + Cvar_SetQuick(variable, value); + if (set_rom) + variable->flags |= CVAR_ROM; +} + +/* +============ +Cvar_SetCallback + +Set a callback function to the var +============ +*/ +void Cvar_SetCallback(cvar_t *var, cvarcallback_t func) { + var->callback = func; + if (func) + var->flags |= CVAR_CALLBACK; + else var->flags &= ~CVAR_CALLBACK; +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command() { + // check variables + auto var = Cvar_FindVar(Cmd_Argv(0)); + if (!var.has_value()) + return false; + + // perform a variable print or set + if (Cmd_Argc() == 1) { + Con_Printf("\"%s\" is \"%s\"\n", var.value()->name, var.value()->string); + return true; + } + + Cvar_Set(var.value()->name, Cmd_Argv(1)); + return true; +} + + +/* +============ +Cvar_WriteVariables + +Writes lines containing "set variable value" for all variables +with the archive flag set to true. +============ +*/ +void Cvar_WriteVariables(FILE *f) { + for (const auto &[name, var] : CLIENT_VARIABLES) { + if (var->flags & CVAR_ARCHIVE) + fprintf(f, "%s \"%s\"\n", name.c_str(), var->string); + } +} diff --git a/Quake/cvar.hpp b/Quake/cvar.hpp new file mode 100644 index 0000000..03b7ca9 --- /dev/null +++ b/Quake/cvar.hpp @@ -0,0 +1,146 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __CVAR_H__ +#define __CVAR_H__ + +/* +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. + +it is sufficient to initialize a cvar_t with just the first two fields, +or you can add a ,true flag for variables that you want saved to the +configuration file when the game is quit: + +cvar_t r_draworder = {"r_draworder","1"}; +cvar_t scr_screensize = {"screensize","1",true}; + +Cvars must be registered before use, or they will have a 0 value instead +of the float interpretation of the string. +Generally, all cvar_t declarations should be registered in the apropriate +init function before any console commands are executed: + +Cvar_RegisterVariable (&host_framerate); + + +C code usually just references a cvar in place: +if ( r_draworder.value ) + +It could optionally ask for the value to be looked up for a string name: +if (Cvar_VariableValue ("r_draworder")) + +Interpreted prog code can access cvars with the cvar(name) or +cvar_set (name, value) internal functions: +teamplay = cvar("teamplay"); +cvar_set ("registered", "1"); + +The user can access cvars from the console in two ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +*/ + +#define CVAR_NONE 0 +#define CVAR_ARCHIVE (1U << 0) // if set, causes it to be saved to config +#define CVAR_NOTIFY (1U << 1) // changes will be broadcasted to all players (q1) +#define CVAR_SERVERINFO (1U << 2) // added to serverinfo will be sent to clients (q1/net_dgrm.c and qwsv) +#define CVAR_USERINFO (1U << 3) // added to userinfo, will be sent to server (qwcl) +#define CVAR_CHANGED (1U << 4) +#define CVAR_ROM (1U << 6) +#define CVAR_LOCKED (1U << 8) // locked temporarily +#define CVAR_REGISTERED (1U << 10) // the var is added to the list of variables +#define CVAR_CALLBACK (1U << 16) // var has a callback + +#include +#include + +typedef void (*cvarcallback_t) (struct cvar_s *); + +typedef struct cvar_s +{ + const char *name; + const char *string; + unsigned int flags; + float value; + const char *default_string; //johnfitz -- remember defaults for reset function + cvarcallback_t callback; +} cvar_t; + +void Cvar_RegisterVariable (cvar_t *variable); +// registers a cvar that already has the name, string, and optionally +// the archive elements set. + +void Cvar_SetCallback (cvar_t *var, cvarcallback_t func); +// set a callback function to the var + +void Cvar_Set(const std::string &name, const std::string &value); +// equivelant to " " typed at the console + +void Cvar_SetValue (const char *var_name, const float value); +// expands value to a string and calls Cvar_Set + +void Cvar_SetROM (const char *var_name, const char *value); +void Cvar_SetValueROM (const char *var_name, const float value); +// sets a CVAR_ROM variable from within the engine + +void Cvar_SetQuick(cvar_t *var, const std::string &value); +void Cvar_SetValueQuick (cvar_t *var, const float value); +// these two accept a cvar pointer instead of a var name, +// but are otherwise identical to the "non-Quick" versions. +// the cvar MUST be registered. + +float Cvar_VariableValue (const char *var_name); +// returns 0 if not defined or non numeric + +const char *Cvar_VariableString (const char *var_name); +// returns an empty string if not defined + +qboolean 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 (FILE *f); +// Writes lines containing "set variable value" for all variables +// with the CVAR_ARCHIVE flag set + +std::optional Cvar_FindVar (const std::string &desired); +cvar_t *Cvar_FindVarAfter (const char *prev_name, unsigned int with_flags); + +void Cvar_LockVar(const std::string &name); +void Cvar_UnlockVar(const std::string &name); +void Cvar_UnlockAll (void); + +void Cvar_Init (void); + +const char *Cvar_CompleteVariable (const char *partial); +// attempts to match a partial variable name for command line completion +// returns NULL if nothing fits + +extern std::map CLIENT_VARIABLES; + +#endif /* __CVAR_H__ */ + diff --git a/Quake/default_cfg.hpp b/Quake/default_cfg.hpp new file mode 100644 index 0000000..a83e7ce --- /dev/null +++ b/Quake/default_cfg.hpp @@ -0,0 +1,92 @@ +// keep in sync with Misc/qs_pak/default.cfg + +static const char default_cfg[] = +"unbindall\n" + +"bind ALT +strafe\n" + +"bind , +moveleft\n" +"bind a +moveleft\n" +"bind . +moveright\n" +"bind d +moveright\n" +"bind DEL +lookdown\n" +"bind PGDN +lookup\n" +"bind END centerview\n" + +"bind e +moveup\n" +"bind c +movedown\n" +"bind SHIFT +speed\n" +"bind CTRL +attack\n" +"bind UPARROW +forward\n" +"bind w +forward\n" +"bind DOWNARROW +back\n" +"bind s +back\n" +"bind LEFTARROW +left\n" +"bind RIGHTARROW +right\n" + +"bind SPACE +jump\n" + +"bind TAB +showscores\n" + +"bind 1 \"impulse 1\"\n" +"bind 2 \"impulse 2\"\n" +"bind 3 \"impulse 3\"\n" +"bind 4 \"impulse 4\"\n" +"bind 5 \"impulse 5\"\n" +"bind 6 \"impulse 6\"\n" +"bind 7 \"impulse 7\"\n" +"bind 8 \"impulse 8\"\n" + +"bind 0 \"impulse 0\"\n" + +"bind / \"impulse 10\"\n" +"bind MWHEELDOWN \"impulse 10\"\n" +"bind MWHEELUP \"impulse 12\"\n" + +"alias zoom_in \"sensitivity 2;fov 90;wait;fov 70;wait;fov 50;wait;fov 30;wait;fov 10;wait;fov 5;bind F11 zoom_out\"\n" +"alias zoom_out \"sensitivity 4;fov 5;wait;fov 10;wait;fov 30;wait;fov 50;wait;fov 70;wait;fov 90;bind F11 zoom_in; sensitivity 3\"\n" +"bind F11 zoom_in\n" + +"bind F1 \"help\"\n" +"bind F2 \"menu_save\"\n" +"bind F3 \"menu_load\"\n" +"bind F4 \"menu_options\"\n" +"bind F5 \"menu_multiplayer\"\n" +"bind F6 \"echo Quicksaving...; wait; save quick\"\n" +"bind F9 \"echo Quickloading...; wait; load quick\"\n" +"bind F10 \"quit\"\n" +"bind F12 \"screenshot\"\n" + +"bind \\ +mlook\n" + +"bind PAUSE \"pause\"\n" +"bind ESCAPE \"togglemenu\"\n" +"bind ~ \"toggleconsole\"\n" +"bind ` \"toggleconsole\"\n" + +"bind t \"messagemode\"\n" + +"bind + \"sizeup\"\n" +"bind = \"sizeup\"\n" +"bind - \"sizedown\"\n" + +"bind INS +klook\n" + +"bind MOUSE1 +attack\n" +"bind MOUSE2 +jump\n" + +"bind LSHOULDER \"impulse 12\"\n" +"bind RSHOULDER \"impulse 10\"\n" +"bind LTRIGGER +jump\n" +"bind RTRIGGER +attack\n" + +"gamma 1.0\n" +"volume 0.7\n" +"sensitivity 3\n" + +"viewsize 110\n" +"scr_conscale 1.6\n" +"scr_menuscale 1.6\n" +"scr_sbarscale 1.6\n" + +"+mlook\n"; diff --git a/Quake/draw.hpp b/Quake/draw.hpp new file mode 100644 index 0000000..396f57e --- /dev/null +++ b/Quake/draw.hpp @@ -0,0 +1,48 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_DRAW_H +#define _QUAKE_DRAW_H + +// draw.h -- these are the only functions outside the refresh allowed +// to touch the vid buffer + +extern qpic_t *draw_disc; // also used on sbar + +void Draw_Init (void); +void Draw_Character (int x, int y, int num); +void Draw_DebugChar (char num); +void Draw_Pic (int x, int y, qpic_t *pic); +void Draw_TransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom); //johnfitz -- more parameters +void Draw_ConsoleBackground (void); //johnfitz -- removed parameter int lines +void Draw_TileClear (int x, int y, int w, int h); +void Draw_Fill (int x, int y, int w, int h, int c, float alpha); //johnfitz -- added alpha +void Draw_FadeScreen (void); +void Draw_String (int x, int y, const char *str); +qpic_t *Draw_PicFromWad (const char *name); +qpic_t *Draw_CachePic (const char *path); +void Draw_NewGame (void); + +void GL_SetCanvas (canvastype newcanvas); //johnfitz + +#endif /* _QUAKE_DRAW_H */ + diff --git a/Quake/filenames.hpp b/Quake/filenames.hpp new file mode 100644 index 0000000..9e4c78f --- /dev/null +++ b/Quake/filenames.hpp @@ -0,0 +1,193 @@ +/* Macros for taking apart, interpreting and processing file names. + * + * These are here because some non-Posix (a.k.a. DOSish) systems have + * drive letter brain-damage at the beginning of an absolute file name, + * use forward- and back-slash in path names interchangeably, and + * some of them have case-insensitive file names. + * + * This was based on filenames.h from BFD, the Binary File Descriptor + * library, Copyright (C) 2000-2016 Free Software Foundation, Inc., + * and changed by O. Sezer for our needs. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FILENAMES_H +#define FILENAMES_H + +#include + +/* ---------------------- Windows, DOS, OS2: ---------------------- */ +#if defined(__MSDOS__) || defined(__DOS__) || defined(__DJGPP__) || \ + defined(_MSDOS) || defined(__OS2__) || defined(__EMX__) || \ + defined(_WIN32) || defined(_Windows) || defined(__WINDOWS__) || \ + defined(__NT__) || defined(__CYGWIN__) + +#define HAVE_DOS_BASED_FILE_SYSTEM 1 +#define HAVE_CASE_INSENSITIVE_FILE_SYSTEM 1 + +#define HAS_DRIVE_SPEC(f) ((f)[0] && ((f)[1] == ':')) +#define STRIP_DRIVE_SPEC(f) ((f) + 2) +#define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') +/* both '/' and '\\' work as dir separator. djgpp likes changing + * '\\' into '/', so I define DIR_SEPARATOR_CHAR as '/' for djgpp, + * '\\' otherwise. */ +#ifdef __DJGPP__ +#define DIR_SEPARATOR_CHAR '/' +#define DIR_SEPARATOR_STR "/" +#else +#define DIR_SEPARATOR_CHAR '\\' +#define DIR_SEPARATOR_STR "\\" +#endif +/* Note that IS_ABSOLUTE_PATH accepts d:foo as well, although it is + only semi-absolute. This is because the users of IS_ABSOLUTE_PATH + want to know whether to prepend the current working directory to + a file name, which should not be done with a name like d:foo. */ +#define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0]) || HAS_DRIVE_SPEC((f))) + +#ifdef __cplusplus +static inline char *FIND_FIRST_DIRSEP(char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strchr(_the_path, '/'); + char *p2 = strchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 < p2)? p1 : p2; +} +static inline char *FIND_LAST_DIRSEP (char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strrchr(_the_path, '/'); + char *p2 = strrchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 > p2)? p1 : p2; +} +static inline const char *FIND_FIRST_DIRSEP(const char *_the_path) { +/* FIXME: What about C:FOO ? */ + const char *p1 = strchr(_the_path, '/'); + const char *p2 = strchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 < p2)? p1 : p2; +} +static inline const char *FIND_LAST_DIRSEP (const char *_the_path) { +/* FIXME: What about C:FOO ? */ + const char *p1 = strrchr(_the_path, '/'); + const char *p2 = strrchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 > p2)? p1 : p2; +} +#else +static inline char *FIND_FIRST_DIRSEP(const char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strchr(_the_path, '/'); + char *p2 = strchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 < p2)? p1 : p2; +} +static inline char *FIND_LAST_DIRSEP (const char *_the_path) { +/* FIXME: What about C:FOO ? */ + char *p1 = strrchr(_the_path, '/'); + char *p2 = strrchr(_the_path, '\\'); + if (p1 == NULL) return p2; + if (p2 == NULL) return p1; + return (p1 > p2)? p1 : p2; +} +#endif /* C++ */ + +/* ----------------- AmigaOS, MorphOS, AROS, etc: ----------------- */ +#elif defined(__MORPHOS__) || defined(__AROS__) || defined(AMIGAOS) || \ + defined(__amigaos__) || defined(__amigaos4__) ||defined(__amigados__) || \ + defined(AMIGA) || defined(_AMIGA) || defined(__AMIGA__) + +#define HAS_DRIVE_SPEC(f) (0) /* */ +#define STRIP_DRIVE_SPEC(f) (f) /* */ +#define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == ':') +#define DIR_SEPARATOR_CHAR '/' +#define DIR_SEPARATOR_STR "/" +#define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0]) || (strchr((f), ':'))) +#define HAVE_CASE_INSENSITIVE_FILE_SYSTEM 1 + +#ifdef __cplusplus +static inline char *FIND_FIRST_DIRSEP(char *_the_path) { + char *p = strchr(_the_path, ':'); + if (p != NULL) return p; + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (char *_the_path) { + char *p = strrchr(_the_path, '/'); + if (p != NULL) return p; + return strchr(_the_path, ':'); +} +static inline const char *FIND_FIRST_DIRSEP(const char *_the_path) { + const char *p = strchr(_the_path, ':'); + if (p != NULL) return p; + return strchr(_the_path, '/'); +} +static inline const char *FIND_LAST_DIRSEP (const char *_the_path) { + const char *p = strrchr(_the_path, '/'); + if (p != NULL) return p; + return strchr(_the_path, ':'); +} +#else +static inline char *FIND_FIRST_DIRSEP(const char *_the_path) { + char *p = strchr(_the_path, ':'); + if (p != NULL) return p; + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (const char *_the_path) { + char *p = strrchr(_the_path, '/'); + if (p != NULL) return p; + return strchr(_the_path, ':'); +} +#endif /* C++ */ + +/* ---------------------- assumed UNIX-ish : ---------------------- */ +#else /* */ + +#define IS_DIR_SEPARATOR(c) ((c) == '/') +#define DIR_SEPARATOR_CHAR '/' +#define DIR_SEPARATOR_STR "/" +#define IS_ABSOLUTE_PATH(f) (IS_DIR_SEPARATOR((f)[0])) +#define HAS_DRIVE_SPEC(f) (0) +#define STRIP_DRIVE_SPEC(f) (f) + +#ifdef __cplusplus +static inline char *FIND_FIRST_DIRSEP(char *_the_path) { + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (char *_the_path) { + return strrchr(_the_path, '/'); +} +static inline const char *FIND_FIRST_DIRSEP(const char *_the_path) { + return strchr(_the_path, '/'); +} +static inline const char *FIND_LAST_DIRSEP (const char *_the_path) { + return strrchr(_the_path, '/'); +} +#else +static inline char *FIND_FIRST_DIRSEP(const char *_the_path) { + return strchr(_the_path, '/'); +} +static inline char *FIND_LAST_DIRSEP (const char *_the_path) { + return strrchr(_the_path, '/'); +} +#endif /* C++ */ + +#endif + +#endif /* FILENAMES_H */ diff --git a/Quake/gl_draw.cpp b/Quake/gl_draw.cpp new file mode 100644 index 0000000..2476380 --- /dev/null +++ b/Quake/gl_draw.cpp @@ -0,0 +1,772 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// draw.c -- 2d drawing + +#include "quakedef.hpp" + +//extern unsigned char d_15to8table[65536]; //johnfitz -- never used + +cvar_t scr_conalpha = {"scr_conalpha", "0.5", CVAR_ARCHIVE}; //johnfitz + +qpic_t *draw_disc; +qpic_t *draw_backtile; + +gltexture_t *char_texture; //johnfitz +qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling +qpic_t *pic_nul; //johnfitz -- for missing gfx, don't crash + +//johnfitz -- new pics +byte pic_ovr_data[8][8] = +{ + {255,255,255,255,255,255,255,255}, + {255, 15, 15, 15, 15, 15, 15,255}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255, 15, 15, 15, 15, 15, 15, 2}, + {255,255, 2, 2, 2, 2, 2, 2}, +}; + +byte pic_ins_data[9][8] = +{ + { 15, 15,255,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + { 15, 15, 2,255,255,255,255,255}, + {255, 2, 2,255,255,255,255,255}, +}; + +byte pic_nul_data[8][8] = +{ + {252,252,252,252, 0, 0, 0, 0}, + {252,252,252,252, 0, 0, 0, 0}, + {252,252,252,252, 0, 0, 0, 0}, + {252,252,252,252, 0, 0, 0, 0}, + { 0, 0, 0, 0,252,252,252,252}, + { 0, 0, 0, 0,252,252,252,252}, + { 0, 0, 0, 0,252,252,252,252}, + { 0, 0, 0, 0,252,252,252,252}, +}; + +byte pic_stipple_data[8][8] = +{ + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, + {255, 0, 0, 0,255, 0, 0, 0}, + { 0, 0,255, 0, 0, 0,255, 0}, +}; + +byte pic_crosshair_data[8][8] = +{ + {255,255,255,255,255,255,255,255}, + {255,255,255, 8, 9,255,255,255}, + {255,255,255, 6, 8, 2,255,255}, + {255, 6, 8, 8, 6, 8, 8,255}, + {255,255, 2, 8, 8, 2, 2, 2}, + {255,255,255, 7, 8, 2,255,255}, + {255,255,255,255, 2, 2,255,255}, + {255,255,255,255,255,255,255,255}, +}; +//johnfitz + +typedef struct +{ + gltexture_t *gltexture; + float sl, tl, sh, th; +} glpic_t; + +canvastype currentcanvas = CANVAS_NONE; //johnfitz -- for GL_SetCanvas + +//============================================================================== +// +// PIC CACHING +// +//============================================================================== + +typedef struct cachepic_s +{ + char name[MAX_QPATH]; + qpic_t pic; + byte padding[32]; // for appended glpic +} cachepic_t; + +#define MAX_CACHED_PICS 128 +cachepic_t menu_cachepics[MAX_CACHED_PICS]; +int menu_numcachepics; + +byte menuplyr_pixels[4096]; + +// scrap allocation +// Allocate all the little status bar obejcts into a single texture +// to crutch up stupid hardware / drivers + +#define MAX_SCRAPS 2 +#define BLOCK_WIDTH 256 +#define BLOCK_HEIGHT 256 + +int scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH]; +byte scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT]; //johnfitz -- removed *4 after BLOCK_HEIGHT +qboolean scrap_dirty; +gltexture_t *scrap_textures[MAX_SCRAPS]; //johnfitz + + +/* +================ +Scrap_AllocBlock + +returns an index into scrap_texnums[] and the position inside it +================ +*/ +int Scrap_AllocBlock (int w, int h, int *x, int *y) +{ + int i, j; + int best, best2; + int texnum; + + for (texnum=0 ; texnum= best) + break; + if (scrap_allocated[texnum][i+j] > best2) + best2 = scrap_allocated[texnum][i+j]; + } + if (j == w) + { // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if (best + h > BLOCK_HEIGHT) + continue; + + for (i=0 ; iwidth < 64 && p->height < 64) + { + int x, y; + int i, j, k; + int texnum; + + texnum = Scrap_AllocBlock (p->width, p->height, &x, &y); + scrap_dirty = true; + k = 0; + for (i=0 ; iheight ; i++) + { + for (j=0 ; jwidth ; j++, k++) + scrap_texels[texnum][(y+i)*BLOCK_WIDTH + x + j] = p->data[k]; + } + gl.gltexture = scrap_textures[texnum]; //johnfitz -- changed to an array + //johnfitz -- no longer go from 0.01 to 0.99 + gl.sl = x/(float)BLOCK_WIDTH; + gl.sh = (x+p->width)/(float)BLOCK_WIDTH; + gl.tl = y/(float)BLOCK_WIDTH; + gl.th = (y+p->height)/(float)BLOCK_WIDTH; + } + else + { + char texturename[64]; //johnfitz + q_snprintf (texturename, sizeof(texturename), "%s:%s", WADFILENAME, name); //johnfitz + + offset = (src_offset_t)p - (src_offset_t)wad_base + sizeof(int)*2; //johnfitz + + gl.gltexture = TexMgr_LoadImage (NULL, texturename, p->width, p->height, SRC_INDEXED, p->data, WADFILENAME, + offset, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + gl.sl = 0; + gl.sh = (float)p->width/(float)TexMgr_PadConditional(p->width); //johnfitz + gl.tl = 0; + gl.th = (float)p->height/(float)TexMgr_PadConditional(p->height); //johnfitz + } + + memcpy (p->data, &gl, sizeof(glpic_t)); + + return p; +} + +/* +================ +Draw_CachePic +================ +*/ +qpic_t *Draw_CachePic (const char *path) +{ + cachepic_t *pic; + int i; + qpic_t *dat; + glpic_t gl; + + for (pic=menu_cachepics, i=0 ; iname)) + return &pic->pic; + } + if (menu_numcachepics == MAX_CACHED_PICS) + Sys_Error ("menu_numcachepics == MAX_CACHED_PICS"); + menu_numcachepics++; + strcpy (pic->name, path); + +// +// load the pic from disk +// + dat = (qpic_t *)COM_LoadTempFile (path, NULL); + if (!dat) + Sys_Error ("Draw_CachePic: failed to load %s", path); + SwapPic (dat); + + // HACK HACK HACK --- we need to keep the bytes for + // the translatable player picture just for the menu + // configuration dialog + if (!strcmp (path, "gfx/menuplyr.lmp")) + memcpy (menuplyr_pixels, dat->data, dat->width*dat->height); + + pic->pic.width = dat->width; + pic->pic.height = dat->height; + + gl.gltexture = TexMgr_LoadImage (NULL, path, dat->width, dat->height, SRC_INDEXED, dat->data, path, + sizeof(int)*2, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + gl.sl = 0; + gl.sh = (float)dat->width/(float)TexMgr_PadConditional(dat->width); //johnfitz + gl.tl = 0; + gl.th = (float)dat->height/(float)TexMgr_PadConditional(dat->height); //johnfitz + memcpy (pic->pic.data, &gl, sizeof(glpic_t)); + + return &pic->pic; +} + +/* +================ +Draw_MakePic -- johnfitz -- generate pics from internal data +================ +*/ +qpic_t *Draw_MakePic (const char *name, int width, int height, byte *data) +{ + int flags = TEXPREF_NEAREST | TEXPREF_ALPHA | TEXPREF_PERSIST | TEXPREF_NOPICMIP | TEXPREF_PAD; + qpic_t *pic; + glpic_t gl; + + pic = (qpic_t *) Hunk_Alloc (sizeof(qpic_t) - 4 + sizeof (glpic_t)); + pic->width = width; + pic->height = height; + + gl.gltexture = TexMgr_LoadImage (NULL, name, width, height, SRC_INDEXED, data, "", (src_offset_t)data, flags); + gl.sl = 0; + gl.sh = (float)width/(float)TexMgr_PadConditional(width); + gl.tl = 0; + gl.th = (float)height/(float)TexMgr_PadConditional(height); + memcpy (pic->data, &gl, sizeof(glpic_t)); + + return pic; +} + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +=============== +Draw_LoadPics -- johnfitz +=============== +*/ +void Draw_LoadPics (void) +{ + byte *data; + src_offset_t offset; + + data = (byte *) W_GetLumpName ("conchars"); + if (!data) Sys_Error ("Draw_LoadPics: couldn't load conchars"); + offset = (src_offset_t)data - (src_offset_t)wad_base; + char_texture = TexMgr_LoadImage (NULL, WADFILENAME":conchars", 128, 128, SRC_INDEXED, data, + WADFILENAME, offset, TEXPREF_ALPHA | TEXPREF_NEAREST | TEXPREF_NOPICMIP | TEXPREF_CONCHARS); + + draw_disc = Draw_PicFromWad ("disc"); + draw_backtile = Draw_PicFromWad ("backtile"); +} + +/* +=============== +Draw_NewGame -- johnfitz +=============== +*/ +void Draw_NewGame (void) +{ + cachepic_t *pic; + int i; + + // empty scrap and reallocate gltextures + memset(scrap_allocated, 0, sizeof(scrap_allocated)); + memset(scrap_texels, 255, sizeof(scrap_texels)); + + Scrap_Upload (); //creates 2 empty gltextures + + // reload wad pics + W_LoadWadFile (); //johnfitz -- filename is now hard-coded for honesty + Draw_LoadPics (); + SCR_LoadPics (); + Sbar_LoadPics (); + + // empty lmp cache + for (pic = menu_cachepics, i = 0; i < menu_numcachepics; pic++, i++) + pic->name[0] = 0; + menu_numcachepics = 0; +} + +/* +=============== +Draw_Init -- johnfitz -- rewritten +=============== +*/ +void Draw_Init (void) +{ + Cvar_RegisterVariable (&scr_conalpha); + + // clear scrap and allocate gltextures + memset(scrap_allocated, 0, sizeof(scrap_allocated)); + memset(scrap_texels, 255, sizeof(scrap_texels)); + + Scrap_Upload (); //creates 2 empty textures + + // create internal pics + pic_ins = Draw_MakePic ("ins", 8, 9, &pic_ins_data[0][0]); + pic_ovr = Draw_MakePic ("ovr", 8, 8, &pic_ovr_data[0][0]); + pic_nul = Draw_MakePic ("nul", 8, 8, &pic_nul_data[0][0]); + + // load game pics + Draw_LoadPics (); +} + +//============================================================================== +// +// 2D DRAWING +// +//============================================================================== + +/* +================ +Draw_CharacterQuad -- johnfitz -- seperate function to spit out verts +================ +*/ +void Draw_CharacterQuad (int x, int y, char num) +{ + int row, col; + float frow, fcol, size; + + row = num>>4; + col = num&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + glTexCoord2f (fcol, frow); + glVertex2f (x, y); + glTexCoord2f (fcol + size, frow); + glVertex2f (x+8, y); + glTexCoord2f (fcol + size, frow + size); + glVertex2f (x+8, y+8); + glTexCoord2f (fcol, frow + size); + glVertex2f (x, y+8); +} + +/* +================ +Draw_Character -- johnfitz -- modified to call Draw_CharacterQuad +================ +*/ +void Draw_Character (int x, int y, int num) +{ + if (y <= -8) + return; // totally off screen + + num &= 255; + + if (num == 32) + return; //don't waste verts on spaces + + GL_Bind (char_texture); + glBegin (GL_QUADS); + + Draw_CharacterQuad (x, y, (char) num); + + glEnd (); +} + +/* +================ +Draw_String -- johnfitz -- modified to call Draw_CharacterQuad +================ +*/ +void Draw_String (int x, int y, const char *str) +{ + if (y <= -8) + return; // totally off screen + + GL_Bind (char_texture); + glBegin (GL_QUADS); + + while (*str) + { + if (*str != 32) //don't waste verts on spaces + Draw_CharacterQuad (x, y, *str); + str++; + x += 8; + } + + glEnd (); +} + +/* +============= +Draw_Pic -- johnfitz -- modified +============= +*/ +void Draw_Pic (int x, int y, qpic_t *pic) +{ + glpic_t *gl; + + if (scrap_dirty) + Scrap_Upload (); + gl = (glpic_t *)pic->data; + GL_Bind (gl->gltexture); + glBegin (GL_QUADS); + glTexCoord2f (gl->sl, gl->tl); + glVertex2f (x, y); + glTexCoord2f (gl->sh, gl->tl); + glVertex2f (x+pic->width, y); + glTexCoord2f (gl->sh, gl->th); + glVertex2f (x+pic->width, y+pic->height); + glTexCoord2f (gl->sl, gl->th); + glVertex2f (x, y+pic->height); + glEnd (); +} + +/* +============= +Draw_TransPicTranslate -- johnfitz -- rewritten to use texmgr to do translation + +Only used for the player color selection menu +============= +*/ +void Draw_TransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom) +{ + static int oldtop = -2; + static int oldbottom = -2; + + if (top != oldtop || bottom != oldbottom) + { + glpic_t *p = (glpic_t *)pic->data; + gltexture_t *glt = p->gltexture; + oldtop = top; + oldbottom = bottom; + TexMgr_ReloadImage (glt, top, bottom); + } + Draw_Pic (x, y, pic); +} + +/* +================ +Draw_ConsoleBackground -- johnfitz -- rewritten +================ +*/ +void Draw_ConsoleBackground (void) +{ + qpic_t *pic; + float alpha; + + pic = Draw_CachePic ("gfx/conback.lmp"); + pic->width = vid.conwidth; + pic->height = vid.conheight; + + alpha = (con_forcedup) ? 1.0f : scr_conalpha.value; + + GL_SetCanvas (CANVAS_CONSOLE); //in case this is called from weird places + + if (alpha > 0.0f) + { + if (alpha < 1.0f) + { + glEnable (GL_BLEND); + glColor4f (1,1,1,alpha); + glDisable (GL_ALPHA_TEST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } + + Draw_Pic (0, 0, pic); + + if (alpha < 1.0f) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glEnable (GL_ALPHA_TEST); + glDisable (GL_BLEND); + glColor4f (1,1,1,1); + } + } +} + + +/* +============= +Draw_TileClear + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +void Draw_TileClear (int x, int y, int w, int h) +{ + glpic_t *gl; + + gl = (glpic_t *)draw_backtile->data; + + glColor3f (1,1,1); + GL_Bind (gl->gltexture); + glBegin (GL_QUADS); + glTexCoord2f (x/64.0, y/64.0); + glVertex2f (x, y); + glTexCoord2f ( (x+w)/64.0, y/64.0); + glVertex2f (x+w, y); + glTexCoord2f ( (x+w)/64.0, (y+h)/64.0); + glVertex2f (x+w, y+h); + glTexCoord2f ( x/64.0, (y+h)/64.0 ); + glVertex2f (x, y+h); + glEnd (); +} + +/* +============= +Draw_Fill + +Fills a box of pixels with a single color +============= +*/ +void Draw_Fill (int x, int y, int w, int h, int c, float alpha) //johnfitz -- added alpha +{ + byte *pal = (byte *)d_8to24table; //johnfitz -- use d_8to24table instead of host_basepal + + glDisable (GL_TEXTURE_2D); + glEnable (GL_BLEND); //johnfitz -- for alpha + glDisable (GL_ALPHA_TEST); //johnfitz -- for alpha + glColor4f (pal[c*4]/255.0, pal[c*4+1]/255.0, pal[c*4+2]/255.0, alpha); //johnfitz -- added alpha + + glBegin (GL_QUADS); + glVertex2f (x,y); + glVertex2f (x+w, y); + glVertex2f (x+w, y+h); + glVertex2f (x, y+h); + glEnd (); + + glColor3f (1,1,1); + glDisable (GL_BLEND); //johnfitz -- for alpha + glEnable (GL_ALPHA_TEST); //johnfitz -- for alpha + glEnable (GL_TEXTURE_2D); +} + +/* +================ +Draw_FadeScreen -- johnfitz -- revised +================ +*/ +void Draw_FadeScreen (void) +{ + GL_SetCanvas (CANVAS_DEFAULT); + + glEnable (GL_BLEND); + glDisable (GL_ALPHA_TEST); + glDisable (GL_TEXTURE_2D); + glColor4f (0, 0, 0, 0.5); + glBegin (GL_QUADS); + + glVertex2f (0,0); + glVertex2f (glwidth, 0); + glVertex2f (glwidth, glheight); + glVertex2f (0, glheight); + + glEnd (); + glColor4f (1,1,1,1); + glEnable (GL_TEXTURE_2D); + glEnable (GL_ALPHA_TEST); + glDisable (GL_BLEND); + + Sbar_Changed(); +} + +/* +================ +GL_SetCanvas -- johnfitz -- support various canvas types +================ +*/ +void GL_SetCanvas (canvastype newcanvas) +{ + extern vrect_t scr_vrect; + float s; + int lines; + + if (newcanvas == currentcanvas) + return; + + currentcanvas = newcanvas; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity (); + + switch(newcanvas) + { + case CANVAS_DEFAULT: + glOrtho (0, glwidth, glheight, 0, -99999, 99999); + glViewport (glx, gly, glwidth, glheight); + break; + case CANVAS_CONSOLE: + lines = vid.conheight - (scr_con_current * vid.conheight / glheight); + glOrtho (0, vid.conwidth, vid.conheight + lines, lines, -99999, 99999); + glViewport (glx, gly, glwidth, glheight); + break; + case CANVAS_MENU: + s = q_min((float)glwidth / 320.0f, (float)glheight / 200.0f); + s = CLAMP (1.0f, scr_menuscale.value, s); + // ericw -- doubled width to 640 to accommodate long keybindings + glOrtho (0, 640, 200, 0, -99999, 99999); + glViewport (glx + (glwidth - 320*s) / 2, gly + (glheight - 200*s) / 2, 640*s, 200*s); + break; + case CANVAS_SBAR: + s = CLAMP (1.0f, scr_sbarscale.value, (float)glwidth / 320.0f); + if (cl.gametype == GAME_DEATHMATCH) + { + glOrtho (0, glwidth / s, 48, 0, -99999, 99999); + glViewport (glx, gly, glwidth, 48*s); + } + else + { + glOrtho (0, 320, 48, 0, -99999, 99999); + glViewport (glx + (glwidth - 320*s) / 2, gly, 320*s, 48*s); + } + break; + case CANVAS_WARPIMAGE: + glOrtho (0, 128, 0, 128, -99999, 99999); + glViewport (glx, gly+glheight-gl_warpimagesize, gl_warpimagesize, gl_warpimagesize); + break; + case CANVAS_CROSSHAIR: //0,0 is center of viewport + s = CLAMP (1.0f, scr_crosshairscale.value, 10.0f); + glOrtho (scr_vrect.width/-2/s, scr_vrect.width/2/s, scr_vrect.height/2/s, scr_vrect.height/-2/s, -99999, 99999); + glViewport (scr_vrect.x, glheight - scr_vrect.y - scr_vrect.height, scr_vrect.width & ~1, scr_vrect.height & ~1); + break; + case CANVAS_BOTTOMLEFT: //used by devstats + s = (float)glwidth/vid.conwidth; //use console scale + glOrtho (0, 320, 200, 0, -99999, 99999); + glViewport (glx, gly, 320*s, 200*s); + break; + case CANVAS_BOTTOMRIGHT: //used by fps/clock + s = (float)glwidth/vid.conwidth; //use console scale + glOrtho (0, 320, 200, 0, -99999, 99999); + glViewport (glx+glwidth-320*s, gly, 320*s, 200*s); + break; + case CANVAS_TOPRIGHT: //used by disc + s = 1; + glOrtho (0, 320, 200, 0, -99999, 99999); + glViewport (glx+glwidth-320*s, gly+glheight-200*s, 320*s, 200*s); + break; + default: + Sys_Error ("GL_SetCanvas: bad canvas type"); + } + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity (); +} + +/* +================ +GL_Set2D -- johnfitz -- rewritten +================ +*/ +void GL_Set2D (void) +{ + currentcanvas = CANVAS_INVALID; + GL_SetCanvas (CANVAS_DEFAULT); + + glDisable (GL_DEPTH_TEST); + glDisable (GL_CULL_FACE); + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + glColor4f (1,1,1,1); +} diff --git a/Quake/gl_fog.cpp b/Quake/gl_fog.cpp new file mode 100644 index 0000000..6972bc5 --- /dev/null +++ b/Quake/gl_fog.cpp @@ -0,0 +1,427 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +//gl_fog.c -- global and volumetric fog + +#include "quakedef.hpp" + +//============================================================================== +// +// GLOBAL FOG +// +//============================================================================== + +#define DEFAULT_DENSITY 0.0 +#define DEFAULT_GRAY 0.3 + +static float fog_density; +static float fog_red; +static float fog_green; +static float fog_blue; + +static float old_density; +static float old_red; +static float old_green; +static float old_blue; + +static float fade_time; //duration of fade +static float fade_done; //time when fade will be done + +/* +============= +Fog_Update + +update internal variables +============= +*/ +void Fog_Update (float density, float red, float green, float blue, float time) +{ + //save previous settings for fade + if (time > 0) + { + //check for a fade in progress + if (fade_done > cl.time) + { + float f; + + f = (fade_done - cl.time) / fade_time; + old_density = f * old_density + (1.0 - f) * fog_density; + old_red = f * old_red + (1.0 - f) * fog_red; + old_green = f * old_green + (1.0 - f) * fog_green; + old_blue = f * old_blue + (1.0 - f) * fog_blue; + } + else + { + old_density = fog_density; + old_red = fog_red; + old_green = fog_green; + old_blue = fog_blue; + } + } + + fog_density = density; + fog_red = red; + fog_green = green; + fog_blue = blue; + fade_time = time; + fade_done = cl.time + time; +} + +/* +============= +Fog_ParseServerMessage + +handle an SVC_FOG message from server +============= +*/ +void Fog_ParseServerMessage (void) +{ + float density, red, green, blue, time; + + density = MSG_ReadByte() / 255.0; + red = MSG_ReadByte() / 255.0; + green = MSG_ReadByte() / 255.0; + blue = MSG_ReadByte() / 255.0; + time = MSG_ReadShort() / 100.0; + if (time < 0.0f) time = 0.0f; + + Fog_Update (density, red, green, blue, time); +} + +/* +============= +Fog_FogCommand_f + +handle the 'fog' console command +============= +*/ +void Fog_FogCommand_f (void) +{ + float d, r, g, b, t; + + switch (Cmd_Argc()) + { + default: + case 1: + Con_Printf("usage:\n"); + Con_Printf(" fog \n"); + Con_Printf(" fog \n"); + Con_Printf(" fog \n"); + Con_Printf("current values:\n"); + Con_Printf(" \"density\" is \"%f\"\n", fog_density); + Con_Printf(" \"red\" is \"%f\"\n", fog_red); + Con_Printf(" \"green\" is \"%f\"\n", fog_green); + Con_Printf(" \"blue\" is \"%f\"\n", fog_blue); + return; + case 2: + d = Q_atof(Cmd_Argv(1)); + t = 0.0f; + r = fog_red; + g = fog_green; + b = fog_blue; + break; + case 3: //TEST + d = Q_atof(Cmd_Argv(1)); + t = Q_atof(Cmd_Argv(2)); + r = fog_red; + g = fog_green; + b = fog_blue; + break; + case 4: + d = fog_density; + t = 0.0f; + r = Q_atof(Cmd_Argv(1)); + g = Q_atof(Cmd_Argv(2)); + b = Q_atof(Cmd_Argv(3)); + break; + case 5: + d = Q_atof(Cmd_Argv(1)); + r = Q_atof(Cmd_Argv(2)); + g = Q_atof(Cmd_Argv(3)); + b = Q_atof(Cmd_Argv(4)); + t = 0.0f; + break; + case 6: //TEST + d = Q_atof(Cmd_Argv(1)); + r = Q_atof(Cmd_Argv(2)); + g = Q_atof(Cmd_Argv(3)); + b = Q_atof(Cmd_Argv(4)); + t = Q_atof(Cmd_Argv(5)); + break; + } + + if (d < 0.0f) d = 0.0f; + if (r < 0.0f) r = 0.0f; + else if (r > 1.0f) r = 1.0f; + if (g < 0.0f) g = 0.0f; + else if (g > 1.0f) g = 1.0f; + if (b < 0.0f) b = 0.0f; + else if (b > 1.0f) b = 1.0f; + Fog_Update(d, r, g, b, t); +} + +/* +============= +Fog_ParseWorldspawn + +called at map load +============= +*/ +void Fog_ParseWorldspawn (void) +{ + char key[128], value[4096]; + const char *data; + + //initially no fog + fog_density = DEFAULT_DENSITY; + fog_red = DEFAULT_GRAY; + fog_green = DEFAULT_GRAY; + fog_blue = DEFAULT_GRAY; + + old_density = DEFAULT_DENSITY; + old_red = DEFAULT_GRAY; + old_green = DEFAULT_GRAY; + old_blue = DEFAULT_GRAY; + + fade_time = 0.0; + fade_done = 0.0; + + data = COM_Parse(cl.worldmodel->entities); + if (!data) + return; // error + if (com_token[0] != '{') + return; // error + while (1) + { + data = COM_Parse(data); + if (!data) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + q_strlcpy(key, com_token + 1, sizeof(key)); + else + q_strlcpy(key, com_token, sizeof(key)); + while (key[0] && key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_ParseEx(data, CPE_ALLOWTRUNC); + if (!data) + return; // error + q_strlcpy(value, com_token, sizeof(value)); + + if (!strcmp("fog", key)) + { + sscanf(value, "%f %f %f %f", &fog_density, &fog_red, &fog_green, &fog_blue); + } + } +} + +/* +============= +Fog_GetColor + +calculates fog color for this frame, taking into account fade times +============= +*/ +float *Fog_GetColor (void) +{ + static float c[4]; + float f; + int i; + + if (fade_done > cl.time) + { + f = (fade_done - cl.time) / fade_time; + c[0] = f * old_red + (1.0 - f) * fog_red; + c[1] = f * old_green + (1.0 - f) * fog_green; + c[2] = f * old_blue + (1.0 - f) * fog_blue; + c[3] = 1.0; + } + else + { + c[0] = fog_red; + c[1] = fog_green; + c[2] = fog_blue; + c[3] = 1.0; + } + + for (i = 0; i < 3; i++) { + c[i] = CLAMP (0.f, c[i], 1.f); + } + + //find closest 24-bit RGB value, so solid-colored sky can match the fog perfectly + for (i = 0; i < 3; i++) { + c[i] = (float)(Q_rint(c[i] * 255)) / 255.0f; + } + + return c; +} + +/* +============= +Fog_GetDensity + +returns current density of fog +============= +*/ +float Fog_GetDensity (void) +{ + float f; + + if (fade_done > cl.time) + { + f = (fade_done - cl.time) / fade_time; + return f * old_density + (1.0 - f) * fog_density; + } + else + return fog_density; +} + +/* +============= +Fog_SetupFrame + +called at the beginning of each frame +============= +*/ +void Fog_SetupFrame (void) +{ + glFogfv(GL_FOG_COLOR, Fog_GetColor()); + glFogf(GL_FOG_DENSITY, Fog_GetDensity() / 64.0); +} + +/* +============= +Fog_EnableGFog + +called before drawing stuff that should be fogged +============= +*/ +void Fog_EnableGFog (void) +{ + if (Fog_GetDensity() > 0) + glEnable(GL_FOG); +} + +/* +============= +Fog_DisableGFog + +called after drawing stuff that should be fogged +============= +*/ +void Fog_DisableGFog (void) +{ + if (Fog_GetDensity() > 0) + glDisable(GL_FOG); +} + +/* +============= +Fog_StartAdditive + +called before drawing stuff that is additive blended -- sets fog color to black +============= +*/ +void Fog_StartAdditive (void) +{ + vec3_t color = {0,0,0}; + + if (Fog_GetDensity() > 0) + glFogfv(GL_FOG_COLOR, color); +} + +/* +============= +Fog_StopAdditive + +called after drawing stuff that is additive blended -- restores fog color +============= +*/ +void Fog_StopAdditive (void) +{ + if (Fog_GetDensity() > 0) + glFogfv(GL_FOG_COLOR, Fog_GetColor()); +} + +//============================================================================== +// +// VOLUMETRIC FOG +// +//============================================================================== + +cvar_t r_vfog = {"r_vfog", "1", CVAR_NONE}; + +void Fog_DrawVFog (void){} +void Fog_MarkModels (void){} + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +============= +Fog_NewMap + +called whenever a map is loaded +============= +*/ +void Fog_NewMap (void) +{ + Fog_ParseWorldspawn (); //for global fog + Fog_MarkModels (); //for volumetric fog +} + +/* +============= +Fog_Init + +called when quake initializes +============= +*/ +void Fog_Init (void) +{ + Cmd_AddCommand ("fog",Fog_FogCommand_f); + + //Cvar_RegisterVariable (&r_vfog); + + //set up global fog + fog_density = DEFAULT_DENSITY; + fog_red = DEFAULT_GRAY; + fog_green = DEFAULT_GRAY; + fog_blue = DEFAULT_GRAY; + + Fog_SetupState (); +} + +/* +============= +Fog_SetupState + +ericw -- moved from Fog_Init, state that needs to be setup when a new context is created +============= +*/ +void Fog_SetupState (void) +{ + glFogi(GL_FOG_MODE, GL_EXP2); +} diff --git a/Quake/gl_mesh.cpp b/Quake/gl_mesh.cpp new file mode 100644 index 0000000..f0e45ff --- /dev/null +++ b/Quake/gl_mesh.cpp @@ -0,0 +1,603 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// gl_mesh.c: triangle model functions + +#include "quakedef.hpp" + + +/* +================================================================= + +ALIAS MODEL DISPLAY LIST GENERATION + +================================================================= +*/ + +static unsigned char used[MAXALIASTRIS]; + +// the command list holds counts and s/t values that are valid for +// every frame +static int commands[MAXALIASTRIS*7 + 1]; +static int numcommands; + +// all frames will have their vertexes rearranged and expanded +// so they are in the order expected by the command list +static int vertexorder[MAXALIASTRIS * 3]; +static int numorder; + +static int stripverts[MAXALIASTRIS + 2]; +static int striptris[MAXALIASTRIS]; +static int stripcount; + +/* +================ +StripLength +================ +*/ +static int StripLength (int starttri, int startv) +{ + int m1, m2; + int j; + mtriangle_t *last, *check; + int k; + + used[starttri] = 2; + + last = &triangles[starttri]; + + stripverts[0] = last->vertindex[(startv)%3]; + stripverts[1] = last->vertindex[(startv+1)%3]; + stripverts[2] = last->vertindex[(startv+2)%3]; + + striptris[0] = starttri; + stripcount = 1; + + m1 = last->vertindex[(startv+2)%3]; + m2 = last->vertindex[(startv+1)%3]; + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] ; jnumtris ; j++, check++) + { + if (check->facesfront != last->facesfront) + continue; + for (k=0 ; k<3 ; k++) + { + if (check->vertindex[k] != m1) + continue; + if (check->vertindex[ (k+1)%3 ] != m2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + if (stripcount & 1) + m2 = check->vertindex[ (k+2)%3 ]; + else + m1 = check->vertindex[ (k+2)%3 ]; + + stripverts[stripcount+2] = check->vertindex[ (k+2)%3 ]; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jnumtris ; j++) + if (used[j] == 2) + used[j] = 0; + + return stripcount; +} + +/* +=========== +FanLength +=========== +*/ +static int FanLength (int starttri, int startv) +{ + int m1, m2; + int j; + mtriangle_t *last, *check; + int k; + + used[starttri] = 2; + + last = &triangles[starttri]; + + stripverts[0] = last->vertindex[(startv)%3]; + stripverts[1] = last->vertindex[(startv+1)%3]; + stripverts[2] = last->vertindex[(startv+2)%3]; + + striptris[0] = starttri; + stripcount = 1; + + m1 = last->vertindex[(startv+0)%3]; + m2 = last->vertindex[(startv+2)%3]; + + + // look for a matching triangle +nexttri: + for (j=starttri+1, check=&triangles[starttri+1] ; jnumtris ; j++, check++) + { + if (check->facesfront != last->facesfront) + continue; + for (k=0 ; k<3 ; k++) + { + if (check->vertindex[k] != m1) + continue; + if (check->vertindex[ (k+1)%3 ] != m2) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if (used[j]) + goto done; + + // the new edge + m2 = check->vertindex[ (k+2)%3 ]; + + stripverts[stripcount+2] = m2; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + goto nexttri; + } + } +done: + + // clear the temp used flags + for (j=starttri+1 ; jnumtris ; j++) + if (used[j] == 2) + used[j] = 0; + + return stripcount; +} + + +/* +================ +BuildTris + +Generate a list of trifans or strips +for the model, which holds for all frames +================ +*/ +static void BuildTris (void) +{ + int i, j, k; + int startv; + float s, t; + int len, bestlen, besttype; + int bestverts[MAXALIASTRIS + 2]; + int besttris[MAXALIASTRIS]; + int type; + + // + // build tristrips + // + numorder = 0; + numcommands = 0; + memset (used, 0, sizeof(used)); + for (i = 0; i < pheader->numtris; i++) + { + // pick an unused triangle and start the trifan + if (used[i]) + continue; + + bestlen = 0; + besttype = 0; + for (type = 0 ; type < 2 ; type++) +// type = 1; + { + for (startv = 0; startv < 3; startv++) + { + if (type == 1) + len = StripLength (i, startv); + else + len = FanLength (i, startv); + if (len > bestlen) + { + besttype = type; + bestlen = len; + for (j = 0; j < bestlen+2; j++) + bestverts[j] = stripverts[j]; + for (j = 0; j < bestlen; j++) + besttris[j] = striptris[j]; + } + } + } + + // mark the tris on the best strip as used + for (j = 0; j < bestlen; j++) + used[besttris[j]] = 1; + + if (besttype == 1) + commands[numcommands++] = (bestlen+2); + else + commands[numcommands++] = -(bestlen+2); + + for (j = 0; j < bestlen+2; j++) + { + int tmp; + + // emit a vertex into the reorder buffer + k = bestverts[j]; + vertexorder[numorder++] = k; + + // emit s/t coords into the commands stream + s = stverts[k].s; + t = stverts[k].t; + if (!triangles[besttris[0]].facesfront && stverts[k].onseam) + s += pheader->skinwidth / 2; // on back side + s = (s + 0.5) / pheader->skinwidth; + t = (t + 0.5) / pheader->skinheight; + + // *(float *)&commands[numcommands++] = s; + // *(float *)&commands[numcommands++] = t; + // NOTE: 4 == sizeof(int) + // == sizeof(float) + memcpy (&tmp, &s, 4); + commands[numcommands++] = tmp; + memcpy (&tmp, &t, 4); + commands[numcommands++] = tmp; + } + } + + commands[numcommands++] = 0; // end of list marker + + Con_DPrintf2 ("%3i tri %3i vert %3i cmd\n", pheader->numtris, numorder, numcommands); +} + +static void GL_MakeAliasModelDisplayLists_VBO (qmodel_t *, aliashdr_t *); +static void GLMesh_LoadVertexBuffer (qmodel_t *m, const aliashdr_t *hdr); + +/* +================ +GL_MakeAliasModelDisplayLists +================ +*/ +void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr) +{ + int i, j; + int *cmds; + trivertx_t *verts; + float hscale, vscale; //johnfitz -- padded skins + int count; //johnfitz -- precompute texcoords for padded skins + int *loadcmds; //johnfitz + aliashdr_t *paliashdr = hdr; // (aliashdr_t *)Mod_Extradata (m); + + //johnfitz -- padded skins + hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth); + vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight); + //johnfitz + +//johnfitz -- generate meshes + Con_DPrintf2 ("meshing %s...\n",m->name); + BuildTris (); + + // save the data out + + paliashdr->poseverts = numorder; + + cmds = (int *) Hunk_Alloc (numcommands * 4); + paliashdr->commands = (byte *)cmds - (byte *)paliashdr; + + //johnfitz -- precompute texcoords for padded skins + loadcmds = commands; + while(1) + { + *cmds++ = count = *loadcmds++; + + if (!count) + break; + + if (count < 0) + count = -count; + + do + { + *(float *)cmds++ = hscale * (*(float *)loadcmds++); + *(float *)cmds++ = vscale * (*(float *)loadcmds++); + } while (--count); + } + //johnfitz + + verts = (trivertx_t *) Hunk_Alloc (paliashdr->numposes * paliashdr->poseverts * sizeof(trivertx_t)); + paliashdr->posedata = (byte *)verts - (byte *)paliashdr; + for (i=0 ; inumposes ; i++) + for (j=0 ; jnumposes * paliashdr->numverts * sizeof(trivertx_t)); + paliashdr->vertexes = (byte *)verts - (byte *)paliashdr; + for (i=0 ; inumposes ; i++) + for (j=0 ; jnumverts ; j++) + verts[i*paliashdr->numverts + j] = poseverts[i][j]; + + // there can never be more than this number of verts and we just put them all on the hunk + // (each vertex can be used twice, once with the original UVs and once with the seam adjustment) + desc = (aliasmesh_t *) Hunk_Alloc (sizeof (aliasmesh_t) * pheader->numverts * 2); + + // there will always be this number of indexes + indexes = (unsigned short *) Hunk_Alloc (sizeof (unsigned short) * pheader->numtris * 3); + + pheader->indexes = (intptr_t) indexes - (intptr_t) pheader; + pheader->meshdesc = (intptr_t) desc - (intptr_t) pheader; + pheader->numindexes = 0; + pheader->numverts_vbo = 0; + + mark = Hunk_LowMark (); + + // each pair of elements in the remap array corresponds to one source vertex + // each value is the final index + 1, or 0 if the corresponding vertex hasn't been emitted yet + remap = (unsigned short *) Hunk_Alloc (paliashdr->numverts * 2 * sizeof (remap[0])); + + for (i = 0; i < pheader->numtris; i++) + { + for (j = 0; j < 3; j++) + { + // index into hdr->vertexes + unsigned short vertindex = triangles[i].vertindex[j]; + + // index into remap table + int v = vertindex * 2; + + // check for back side + if (!triangles[i].facesfront && stverts[vertindex].onseam) + v++; + + // emit new vertex if it doesn't already exist + if (!remap[v]) + { + // basic s/t coords + int s = stverts[vertindex].s; + int t = stverts[vertindex].t; + + // check for back side and adjust texcoord s + if (v & 1) + s += paliashdr->skinwidth / 2; + + desc[pheader->numverts_vbo].vertindex = vertindex; + desc[pheader->numverts_vbo].st[0] = s; + desc[pheader->numverts_vbo].st[1] = t; + + remap[v] = ++pheader->numverts_vbo; + } + + // emit index + indexes[pheader->numindexes++] = remap[v] - 1; + } + } + + // free temporary data + Hunk_FreeToLowMark (mark); + + // upload immediately + GLMesh_LoadVertexBuffer (aliasmodel, pheader); +} + +/* +================ +GLMesh_LoadVertexBuffer + +Upload the given alias model's mesh to a VBO + +Original code by MH from RMQEngine +================ +*/ +static void GLMesh_LoadVertexBuffer (qmodel_t *m, const aliashdr_t *hdr) +{ + int totalvbosize = 0; + const aliasmesh_t *desc; + const short *indexes; + const trivertx_t *trivertexes; + byte *vbodata; + int f; + + if (!gl_glsl_alias_able) + return; + +// count the sizes we need + + // ericw -- RMQEngine stored these vbo*ofs values in aliashdr_t, but we must not + // mutate Mod_Extradata since it might be reloaded from disk, so I moved them to qmodel_t + // (test case: roman1.bsp from arwop, 64mb heap) + m->vboindexofs = 0; + + m->vboxyzofs = 0; + totalvbosize += (hdr->numposes * hdr->numverts_vbo * sizeof (meshxyz_t)); // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + + m->vbostofs = totalvbosize; + totalvbosize += (hdr->numverts_vbo * sizeof (meshst_t)); + + if (!hdr->numindexes) return; + if (!totalvbosize) return; + +// grab the pointers to data in the extradata + + desc = (aliasmesh_t *) ((byte *) hdr + hdr->meshdesc); + indexes = (short *) ((byte *) hdr + hdr->indexes); + trivertexes = (trivertx_t *) ((byte *)hdr + hdr->vertexes); + +// upload indices buffer + + GL_DeleteBuffersFunc (1, &m->meshindexesvbo); + GL_GenBuffersFunc (1, &m->meshindexesvbo); + GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, m->meshindexesvbo); + GL_BufferDataFunc (GL_ELEMENT_ARRAY_BUFFER, hdr->numindexes * sizeof (unsigned short), indexes, GL_STATIC_DRAW); + +// create the vertex buffer (empty) + + vbodata = (byte *) malloc(totalvbosize); + memset(vbodata, 0, totalvbosize); + +// fill in the vertices at the start of the buffer + for (f = 0; f < hdr->numposes; f++) // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm + { + int v; + meshxyz_t *xyz = (meshxyz_t *) (vbodata + (f * hdr->numverts_vbo * sizeof (meshxyz_t))); + const trivertx_t *tv = trivertexes + (hdr->numverts * f); + + for (v = 0; v < hdr->numverts_vbo; v++) + { + trivertx_t trivert = tv[desc[v].vertindex]; + + xyz[v].xyz[0] = trivert.v[0]; + xyz[v].xyz[1] = trivert.v[1]; + xyz[v].xyz[2] = trivert.v[2]; + xyz[v].xyz[3] = 1; // need w 1 for 4 byte vertex compression + + // map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char. + // this introduces some error (less than 0.004), but the normals were very coarse + // to begin with + xyz[v].normal[0] = 127 * r_avertexnormals[trivert.lightnormalindex][0]; + xyz[v].normal[1] = 127 * r_avertexnormals[trivert.lightnormalindex][1]; + xyz[v].normal[2] = 127 * r_avertexnormals[trivert.lightnormalindex][2]; + xyz[v].normal[3] = 0; // unused; for 4-byte alignment + } + } + +// fill in the ST coords at the end of the buffer + { + meshst_t *st; + float hscale, vscale; + + //johnfitz -- padded skins + hscale = 1.0f / (float)TexMgr_PadConditional(hdr->skinwidth); + vscale = 1.0f / (float)TexMgr_PadConditional(hdr->skinheight); + //johnfitz + + st = (meshst_t *) (vbodata + m->vbostofs); + for (f = 0; f < hdr->numverts_vbo; f++) + { + st[f].st[0] = hscale * ((float) desc[f].st[0] + 0.5f); + st[f].st[1] = vscale * ((float) desc[f].st[1] + 0.5f); + } + } + +// upload vertexes buffer + GL_DeleteBuffersFunc (1, &m->meshvbo); + GL_GenBuffersFunc (1, &m->meshvbo); + GL_BindBufferFunc (GL_ARRAY_BUFFER, m->meshvbo); + GL_BufferDataFunc (GL_ARRAY_BUFFER, totalvbosize, vbodata, GL_STATIC_DRAW); + + free (vbodata); + +// invalidate the cached bindings + GL_ClearBufferBindings (); +} + +/* +================ +GLMesh_LoadVertexBuffers + +Loop over all precached alias models, and upload each one to a VBO. +================ +*/ +void GLMesh_LoadVertexBuffers (void) +{ + int j; + qmodel_t *m; + const aliashdr_t *hdr; + + if (!gl_glsl_alias_able) + return; + + for (j = 1; j < MAX_MODELS; j++) + { + if (!(m = cl.model_precache[j])) break; + if (m->type != mod_alias) continue; + + hdr = (const aliashdr_t *) Mod_Extradata (m); + + GLMesh_LoadVertexBuffer (m, hdr); + } +} + +/* +================ +GLMesh_DeleteVertexBuffers + +Delete VBOs for all loaded alias models +================ +*/ +void GLMesh_DeleteVertexBuffers (void) +{ + int j; + qmodel_t *m; + + if (!gl_glsl_alias_able) + return; + + for (j = 1; j < MAX_MODELS; j++) + { + if (!(m = cl.model_precache[j])) break; + if (m->type != mod_alias) continue; + + GL_DeleteBuffersFunc (1, &m->meshvbo); + m->meshvbo = 0; + + GL_DeleteBuffersFunc (1, &m->meshindexesvbo); + m->meshindexesvbo = 0; + } + + GL_ClearBufferBindings (); +} diff --git a/Quake/gl_model.cpp b/Quake/gl_model.cpp new file mode 100644 index 0000000..958928a --- /dev/null +++ b/Quake/gl_model.cpp @@ -0,0 +1,3094 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// models.c -- model loading and caching + +// models are the only shared resource between a client and server running +// on the same machine. + +#include "quakedef.hpp" + +static qmodel_t* loadmodel; +static char loadname[32]; // for hunk tags + +static void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer); +static void Mod_LoadBrushModel (qmodel_t *mod, void *buffer); +static void Mod_LoadAliasModel (qmodel_t *mod, void *buffer); +static qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash); + +static void Mod_Print (void); + +static cvar_t external_ents = {"external_ents", "1", CVAR_ARCHIVE}; +static cvar_t external_vis = {"external_vis", "1", CVAR_ARCHIVE}; + +static byte *mod_novis; +static int mod_novis_capacity; + +static byte *mod_decompressed; +static int mod_decompressed_capacity; + +#define MAX_MOD_KNOWN 2048 /*johnfitz -- was 512 */ +static qmodel_t mod_known[MAX_MOD_KNOWN]; +static int mod_numknown; + +texture_t *r_notexture_mip; //johnfitz -- moved here from r_main.c +texture_t *r_notexture_mip2; //johnfitz -- used for non-lightmapped surfs with a missing texture + +/* +=============== +Mod_Init +=============== +*/ +void Mod_Init (void) +{ + Cvar_RegisterVariable (&gl_subdivide_size); + Cvar_RegisterVariable (&external_vis); + Cvar_RegisterVariable (&external_ents); + + Cmd_AddCommand ("mcache", Mod_Print); + + //johnfitz -- create notexture miptex + r_notexture_mip = (texture_t *) Hunk_AllocName (sizeof(texture_t), "r_notexture_mip"); + strcpy (r_notexture_mip->name, "notexture"); + r_notexture_mip->height = r_notexture_mip->width = 32; + + r_notexture_mip2 = (texture_t *) Hunk_AllocName (sizeof(texture_t), "r_notexture_mip2"); + strcpy (r_notexture_mip2->name, "notexture2"); + r_notexture_mip2->height = r_notexture_mip2->width = 32; + //johnfitz +} + +/* +=============== +Mod_Extradata + +Caches the data if needed +=============== +*/ +void *Mod_Extradata (qmodel_t *mod) +{ + void *r; + + r = Cache_Check (&mod->cache); + if (r) + return r; + + Mod_LoadModel (mod, true); + + if (!mod->cache.data) + Sys_Error ("Mod_Extradata: caching failed"); + return mod->cache.data; +} + +/* +=============== +Mod_PointInLeaf +=============== +*/ +mleaf_t *Mod_PointInLeaf (vec3_t p, qmodel_t *model) +{ + mnode_t *node; + float d; + mplane_t *plane; + + if (!model || !model->nodes) + Sys_Error ("Mod_PointInLeaf: bad model"); + + node = model->nodes; + while (1) + { + if (node->contents < 0) + return (mleaf_t *)node; + plane = node->plane; + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return NULL; // never reached +} + + +/* +=================== +Mod_DecompressVis +=================== +*/ +static byte *Mod_DecompressVis (byte *in, qmodel_t *model) +{ + int c; + byte *out; + byte *outend; + int row; + + row = (model->numleafs+7)>>3; + if (mod_decompressed == NULL || row > mod_decompressed_capacity) + { + mod_decompressed_capacity = row; + mod_decompressed = (byte *) realloc (mod_decompressed, mod_decompressed_capacity); + if (!mod_decompressed) + Sys_Error ("Mod_DecompressVis: realloc() failed on %d bytes", mod_decompressed_capacity); + } + out = mod_decompressed; + outend = mod_decompressed + row; + + if (!in) + { // no vis info, so make all visible + while (row) + { + *out++ = 0xff; + row--; + } + return mod_decompressed; + } + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while (c) + { + if (out == outend) + { + if(!model->viswarn) { + model->viswarn = true; + Con_Warning("Mod_DecompressVis: output overrun on model \"%s\"\n", model->name); + } + return mod_decompressed; + } + *out++ = 0; + c--; + } + } while (out - mod_decompressed < row); + + return mod_decompressed; +} + +byte *Mod_LeafPVS (mleaf_t *leaf, qmodel_t *model) +{ + if (leaf == model->leafs) + return Mod_NoVisPVS (model); + return Mod_DecompressVis (leaf->compressed_vis, model); +} + +byte *Mod_NoVisPVS (qmodel_t *model) +{ + int pvsbytes; + + pvsbytes = (model->numleafs+7)>>3; + if (mod_novis == NULL || pvsbytes > mod_novis_capacity) + { + mod_novis_capacity = pvsbytes; + mod_novis = (byte *) realloc (mod_novis, mod_novis_capacity); + if (!mod_novis) + Sys_Error ("Mod_NoVisPVS: realloc() failed on %d bytes", mod_novis_capacity); + + memset(mod_novis, 0xff, mod_novis_capacity); + } + return mod_novis; +} + +/* +=================== +Mod_ClearAll +=================== +*/ +void Mod_ClearAll (void) +{ + int i; + qmodel_t *mod; + + for (i=0 , mod=mod_known ; itype != mod_alias) + { + mod->needload = true; + TexMgr_FreeTexturesForOwner (mod); //johnfitz + } + } +} + +void Mod_ResetAll (void) +{ + int i; + qmodel_t *mod; + + //ericw -- free alias model VBOs + GLMesh_DeleteVertexBuffers (); + + for (i=0 , mod=mod_known ; ineedload) //otherwise Mod_ClearAll() did it already + TexMgr_FreeTexturesForOwner (mod); + memset(mod, 0, sizeof(qmodel_t)); + } + mod_numknown = 0; +} + +/* +================== +Mod_FindName + +================== +*/ +static qmodel_t *Mod_FindName (const char *name) +{ + int i; + qmodel_t *mod; + + if (!name[0]) + Sys_Error ("Mod_FindName: NULL name"); //johnfitz -- was "Mod_ForName" + +// +// search the currently loaded models +// + for (i=0 , mod=mod_known ; iname, name) ) + break; + } + + if (i == mod_numknown) + { + if (mod_numknown == MAX_MOD_KNOWN) + Sys_Error ("mod_numknown == MAX_MOD_KNOWN"); + q_strlcpy (mod->name, name, MAX_QPATH); + mod->needload = true; + mod_numknown++; + } + + return mod; +} + +/* +================== +Mod_TouchModel + +================== +*/ +void Mod_TouchModel (const char *name) +{ + qmodel_t *mod; + + mod = Mod_FindName (name); + + if (!mod->needload) + { + if (mod->type == mod_alias) + Cache_Check (&mod->cache); + } +} + +/* +================== +Mod_LoadModel + +Loads a model into the cache +================== +*/ +static qmodel_t *Mod_LoadModel (qmodel_t *mod, qboolean crash) +{ + byte *buf; + byte stackbuf[1024]; // avoid dirtying the cache heap + int mod_type; + + if (!mod->needload) + { + if (mod->type == mod_alias) + { + if (Cache_Check (&mod->cache)) + return mod; + } + else + return mod; // not cached at all + } + +// +// because the world is so huge, load it one piece at a time +// + if (!crash) + { + + } + +// +// load the file +// + buf = COM_LoadStackFile (mod->name, stackbuf, sizeof(stackbuf), & mod->path_id); + if (!buf) + { + if (crash) + Host_Error ("Mod_LoadModel: %s not found", mod->name); //johnfitz -- was "Mod_NumForName" + return NULL; + } + +// +// allocate a new model +// + COM_FileBase (mod->name, loadname, sizeof(loadname)); + + loadmodel = mod; + +// +// fill it in +// + +// call the apropriate loader + mod->needload = false; + + mod_type = (buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24)); + switch (mod_type) + { + case IDPOLYHEADER: + Mod_LoadAliasModel (mod, buf); + break; + + case IDSPRITEHEADER: + Mod_LoadSpriteModel (mod, buf); + break; + + default: + Mod_LoadBrushModel (mod, buf); + break; + } + + return mod; +} + +/* +================== +Mod_ForName + +Loads in a model for the given name +================== +*/ +qmodel_t *Mod_ForName (const char *name, qboolean crash) +{ + qmodel_t *mod; + + mod = Mod_FindName (name); + + return Mod_LoadModel (mod, crash); +} + + +/* +=============================================================================== + + BRUSHMODEL LOADING + +=============================================================================== +*/ + +static byte *mod_base; + +/* +================= +Mod_CheckFullbrights -- johnfitz +================= +*/ +static qboolean Mod_CheckFullbrights (byte *pixels, int count) +{ + int i; + for (i = 0; i < count; i++) + { + if (*pixels++ > 223) + return true; + } + return false; +} + +/* +================= +Mod_CheckAnimTextureArrayQ64 + +Quake64 bsp +Check if we have any missing textures in the array +================= +*/ +static qboolean Mod_CheckAnimTextureArrayQ64(texture_t *anims[], int numTex) +{ + int i; + + for (i = 0; i < numTex; i++) + { + if (!anims[i]) + return false; + } + return true; +} + +/* +================= +Mod_LoadTextures +================= +*/ +static void Mod_LoadTextures (lump_t *l) +{ + int i, j, pixels, num, maxanim, altmax; + miptex_t *mt; + texture_t *tx, *tx2; + texture_t *anims[10]; + texture_t *altanims[10]; + dmiptexlump_t *m; +//johnfitz -- more variables + char texturename[64]; + int nummiptex; + src_offset_t offset; + int mark, fwidth, fheight; + char filename[MAX_OSPATH], mapname[MAX_OSPATH]; + byte *data; + extern byte *hunk_base; +//johnfitz + unsigned int flags; + + //johnfitz -- don't return early if no textures; still need to create dummy texture + if (!l->filelen) + { + Con_Printf ("Mod_LoadTextures: no textures in bsp file\n"); + nummiptex = 0; + m = NULL; // avoid bogus compiler warning + } + else + { + m = (dmiptexlump_t *)(mod_base + l->fileofs); + m->nummiptex = LittleLong (m->nummiptex); + nummiptex = m->nummiptex; + } + //johnfitz + + loadmodel->numtextures = nummiptex + 2; //johnfitz -- need 2 dummy texture chains for missing textures + loadmodel->textures = (texture_t **) Hunk_AllocName (loadmodel->numtextures * sizeof(*loadmodel->textures) , loadname); + + for (i=0 ; idataofs[i] = LittleLong(m->dataofs[i]); + if (m->dataofs[i] == -1) + continue; + mt = (miptex_t *)((byte *)m + m->dataofs[i]); + mt->width = LittleLong (mt->width); + mt->height = LittleLong (mt->height); + for (j=0 ; joffsets[j] = LittleLong (mt->offsets[j]); + + if (mt->width == 0 || mt->height == 0) + { + Con_Warning ("Zero sized texture %s in %s!\n", mt->name, loadmodel->name); + continue; + } + + if ( (mt->width & 15) || (mt->height & 15) ) + { + if (loadmodel->bspversion != BSPVERSION_QUAKE64) + Con_Warning ("Texture %s (%d x %d) is not 16 aligned\n", mt->name, mt->width, mt->height); + } + + pixels = mt->width*mt->height; // only copy the first mip, the rest are auto-generated + tx = (texture_t *) Hunk_AllocName (sizeof(texture_t) +pixels, loadname ); + loadmodel->textures[i] = tx; + + memcpy (tx->name, mt->name, sizeof(tx->name)); + tx->width = mt->width; + tx->height = mt->height; + // the pixels immediately follow the structures + + // ericw -- check for pixels extending past the end of the lump. + // appears in the wild; e.g. jam2_tronyn.bsp (func_mapjam2), + // kellbase1.bsp (quoth), and can lead to a segfault if we read past + // the end of the .bsp file buffer + if (((byte*)(mt+1) + pixels) > (mod_base + l->fileofs + l->filelen)) + { + Con_DPrintf("Texture %s extends past end of lump\n", mt->name); + pixels = q_max(0L, (long)((mod_base + l->fileofs + l->filelen) - (byte*)(mt+1))); + } + + tx->update_warp = false; //johnfitz + tx->warpimage = NULL; //johnfitz + tx->fullbright = NULL; //johnfitz + tx->shift = 0; // Q64 only + + if (loadmodel->bspversion != BSPVERSION_QUAKE64) + { + memcpy ( tx+1, mt+1, pixels); + } + else + { // Q64 bsp + miptex64_t *mt64 = (miptex64_t *)mt; + tx->shift = LittleLong (mt64->shift); + memcpy ( tx+1, mt64+1, pixels); + } + + //johnfitz -- lots of changes + if (!isDedicated) //no texture uploading for dedicated server + { + if (!q_strncasecmp(tx->name,"sky",3)) //sky texture //also note -- was Q_strncmp, changed to match qbsp + { + if (loadmodel->bspversion == BSPVERSION_QUAKE64) + Sky_LoadTextureQ64 (loadmodel, tx); + else + Sky_LoadTexture (loadmodel, tx); + } + else if (tx->name[0] == '*') //warping texture + { + //external textures -- first look in "textures/mapname/" then look in "textures/" + mark = Hunk_LowMark(); + COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); + q_snprintf (filename, sizeof(filename), "textures/%s/#%s", mapname, tx->name+1); //this also replaces the '*' with a '#' + data = Image_LoadImage (filename, &fwidth, &fheight); + if (!data) + { + q_snprintf (filename, sizeof(filename), "textures/#%s", tx->name+1); + data = Image_LoadImage (filename, &fwidth, &fheight); + } + + //now load whatever we found + if (data) //load external image + { + q_strlcpy (texturename, filename, sizeof(texturename)); + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, fwidth, fheight, + SRC_RGBA, data, filename, 0, TEXPREF_NONE); + } + else //use the texture from the bsp file + { + q_snprintf (texturename, sizeof(texturename), "%s:%s", loadmodel->name, tx->name); + offset = (src_offset_t)(mt+1) - (src_offset_t)mod_base; + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_NONE); + } + + //now create the warpimage, using dummy data from the hunk to create the initial image + Hunk_Alloc (gl_warpimagesize*gl_warpimagesize*4); //make sure hunk is big enough so we don't reach an illegal address + Hunk_FreeToLowMark (mark); + q_snprintf (texturename, sizeof(texturename), "%s_warp", texturename); + flags = TEXPREF_NOPICMIP | TEXPREF_WARPIMAGE; + if (GL_GenerateMipmap) + flags |= TEXPREF_MIPMAP; + tx->warpimage = TexMgr_LoadImage (loadmodel, texturename, gl_warpimagesize, + gl_warpimagesize, SRC_RGBA, hunk_base, "", (src_offset_t)hunk_base, flags); + tx->update_warp = true; + } + else //regular texture + { + // ericw -- fence textures + int extraflags; + + extraflags = 0; + if (tx->name[0] == '{') + extraflags |= TEXPREF_ALPHA; + // ericw + + //external textures -- first look in "textures/mapname/" then look in "textures/" + mark = Hunk_LowMark (); + COM_StripExtension (loadmodel->name + 5, mapname, sizeof(mapname)); + q_snprintf (filename, sizeof(filename), "textures/%s/%s", mapname, tx->name); + data = Image_LoadImage (filename, &fwidth, &fheight); + if (!data) + { + q_snprintf (filename, sizeof(filename), "textures/%s", tx->name); + data = Image_LoadImage (filename, &fwidth, &fheight); + } + + //now load whatever we found + if (data) //load external image + { + char filename2[MAX_OSPATH]; + tx->gltexture = TexMgr_LoadImage (loadmodel, filename, fwidth, fheight, + SRC_RGBA, data, filename, 0, TEXPREF_MIPMAP | extraflags ); + + //now try to load glow/luma image from the same place + Hunk_FreeToLowMark (mark); + q_snprintf (filename2, sizeof(filename2), "%s_glow", filename); + data = Image_LoadImage (filename2, &fwidth, &fheight); + if (!data) + { + q_snprintf (filename2, sizeof(filename2), "%s_luma", filename); + data = Image_LoadImage (filename2, &fwidth, &fheight); + } + + if (data) + tx->fullbright = TexMgr_LoadImage (loadmodel, filename2, fwidth, fheight, + SRC_RGBA, data, filename2, 0, TEXPREF_MIPMAP | extraflags ); + } + else //use the texture from the bsp file + { + q_snprintf (texturename, sizeof(texturename), "%s:%s", loadmodel->name, tx->name); + offset = (src_offset_t)(mt+1) - (src_offset_t)mod_base; + if (Mod_CheckFullbrights ((byte *)(tx+1), pixels)) + { + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | TEXPREF_NOBRIGHT | extraflags); + q_snprintf (texturename, sizeof(texturename), "%s:%s_glow", loadmodel->name, tx->name); + tx->fullbright = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | TEXPREF_FULLBRIGHT | extraflags); + } + else + { + tx->gltexture = TexMgr_LoadImage (loadmodel, texturename, tx->width, tx->height, + SRC_INDEXED, (byte *)(tx+1), loadmodel->name, offset, TEXPREF_MIPMAP | extraflags); + } + } + Hunk_FreeToLowMark (mark); + } + } + //johnfitz + } + + //johnfitz -- last 2 slots in array should be filled with dummy textures + loadmodel->textures[loadmodel->numtextures-2] = r_notexture_mip; //for lightmapped surfs + loadmodel->textures[loadmodel->numtextures-1] = r_notexture_mip2; //for SURF_DRAWTILED surfs + +// +// sequence the animations +// + for (i=0 ; itextures[i]; + if (!tx || tx->name[0] != '+') + continue; + if (tx->anim_next) + continue; // already sequenced + + // find the number of frames in the animation + memset (anims, 0, sizeof(anims)); + memset (altanims, 0, sizeof(altanims)); + + maxanim = tx->name[1]; + altmax = 0; + if (maxanim >= 'a' && maxanim <= 'z') + maxanim -= 'a' - 'A'; + if (maxanim >= '0' && maxanim <= '9') + { + maxanim -= '0'; + altmax = 0; + anims[maxanim] = tx; + maxanim++; + } + else if (maxanim >= 'A' && maxanim <= 'J') + { + altmax = maxanim - 'A'; + maxanim = 0; + altanims[altmax] = tx; + altmax++; + } + else + Sys_Error ("Bad animating texture %s", tx->name); + + for (j=i+1 ; jtextures[j]; + if (!tx2 || tx2->name[0] != '+') + continue; + if (strcmp (tx2->name+2, tx->name+2)) + continue; + + num = tx2->name[1]; + if (num >= 'a' && num <= 'z') + num -= 'a' - 'A'; + if (num >= '0' && num <= '9') + { + num -= '0'; + anims[num] = tx2; + if (num+1 > maxanim) + maxanim = num + 1; + } + else if (num >= 'A' && num <= 'J') + { + num = num - 'A'; + altanims[num] = tx2; + if (num+1 > altmax) + altmax = num+1; + } + else + Sys_Error ("Bad animating texture %s", tx->name); + } + + if (loadmodel->bspversion == BSPVERSION_QUAKE64 && !Mod_CheckAnimTextureArrayQ64(anims, maxanim)) + continue; // Just pretend this is a normal texture + +#define ANIM_CYCLE 2 + // link them all together + for (j=0 ; jname); + tx2->anim_total = maxanim * ANIM_CYCLE; + tx2->anim_min = j * ANIM_CYCLE; + tx2->anim_max = (j+1) * ANIM_CYCLE; + tx2->anim_next = anims[ (j+1)%maxanim ]; + if (altmax) + tx2->alternate_anims = altanims[0]; + } + for (j=0 ; jname); + tx2->anim_total = altmax * ANIM_CYCLE; + tx2->anim_min = j * ANIM_CYCLE; + tx2->anim_max = (j+1) * ANIM_CYCLE; + tx2->anim_next = altanims[ (j+1)%altmax ]; + if (maxanim) + tx2->alternate_anims = anims[0]; + } + } +} + +/* +================= +Mod_LoadLighting -- johnfitz -- replaced with lit support code via lordhavoc +================= +*/ +static void Mod_LoadLighting (lump_t *l) +{ + int i, mark; + byte *in, *out, *data; + byte d, q64_b0, q64_b1; + char litfilename[MAX_OSPATH]; + unsigned int path_id; + + loadmodel->lightdata = NULL; + // LordHavoc: check for a .lit file + q_strlcpy(litfilename, loadmodel->name, sizeof(litfilename)); + COM_StripExtension(litfilename, litfilename, sizeof(litfilename)); + q_strlcat(litfilename, ".lit", sizeof(litfilename)); + mark = Hunk_LowMark(); + data = (byte*) COM_LoadHunkFile (litfilename, &path_id); + if (data) + { + // use lit file only from the same gamedir as the map + // itself or from a searchpath with higher priority. + if (path_id < loadmodel->path_id) + { + Hunk_FreeToLowMark(mark); + Con_DPrintf("ignored %s from a gamedir with lower priority\n", litfilename); + } + else + if (data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') + { + i = LittleLong(((int *)data)[1]); + if (i == 1) + { + if (8+l->filelen*3 == com_filesize) + { + Con_DPrintf2("%s loaded\n", litfilename); + loadmodel->lightdata = data + 8; + return; + } + Hunk_FreeToLowMark(mark); + Con_Printf("Outdated .lit file (%s should be %u bytes, not %u)\n", litfilename, 8+l->filelen*3, com_filesize); + } + else + { + Hunk_FreeToLowMark(mark); + Con_Printf("Unknown .lit file version (%d)\n", i); + } + } + else + { + Hunk_FreeToLowMark(mark); + Con_Printf("Corrupt .lit file (old version?), ignoring\n"); + } + } + // LordHavoc: no .lit found, expand the white lighting data to color + if (!l->filelen) + return; + + // Quake64 bsp lighmap data + if (loadmodel->bspversion == BSPVERSION_QUAKE64) + { + // RGB lightmap samples are packed in 16bits. + // RRRRR GGGGG BBBBBB + + loadmodel->lightdata = (byte *) Hunk_AllocName ( (l->filelen / 2)*3, litfilename); + in = mod_base + l->fileofs; + out = loadmodel->lightdata; + + for (i = 0;i < (l->filelen / 2) ;i++) + { + q64_b0 = *in++; + q64_b1 = *in++; + + *out++ = q64_b0 & 0xf8;/* 0b11111000 */ + *out++ = ((q64_b0 & 0x07) << 5) + ((q64_b1 & 0xc0) >> 5);/* 0b00000111, 0b11000000 */ + *out++ = (q64_b1 & 0x3f) << 2;/* 0b00111111 */ + } + return; + } + + loadmodel->lightdata = (byte *) Hunk_AllocName ( l->filelen*3, litfilename); + in = loadmodel->lightdata + l->filelen*2; // place the file at the end, so it will not be overwritten until the very last write + out = loadmodel->lightdata; + memcpy (in, mod_base + l->fileofs, l->filelen); + for (i = 0;i < l->filelen;i++) + { + d = *in++; + *out++ = d; + *out++ = d; + *out++ = d; + } +} + + +/* +================= +Mod_LoadVisibility +================= +*/ +static void Mod_LoadVisibility (lump_t *l) +{ + loadmodel->viswarn = false; + if (!l->filelen) + { + loadmodel->visdata = NULL; + return; + } + loadmodel->visdata = (byte *) Hunk_AllocName ( l->filelen, loadname); + memcpy (loadmodel->visdata, mod_base + l->fileofs, l->filelen); +} + + +/* +================= +Mod_LoadEntities +================= +*/ +static void Mod_LoadEntities (lump_t *l) +{ + char basemapname[MAX_QPATH]; + char entfilename[MAX_QPATH]; + char *ents; + int mark; + unsigned int path_id; + unsigned int crc = 0; + + if (! external_ents.value) + goto _load_embedded; + + mark = Hunk_LowMark(); + if (l->filelen > 0) { + crc = CRC_Block(mod_base + l->fileofs, l->filelen - 1); + } + + q_strlcpy(basemapname, loadmodel->name, sizeof(basemapname)); + COM_StripExtension(basemapname, basemapname, sizeof(basemapname)); + + q_snprintf(entfilename, sizeof(entfilename), "%s@%04x.ent", basemapname, crc); + Con_DPrintf2("trying to load %s\n", entfilename); + ents = (char *) COM_LoadHunkFile (entfilename, &path_id); + + if (!ents) + { + q_snprintf(entfilename, sizeof(entfilename), "%s.ent", basemapname); + Con_DPrintf2("trying to load %s\n", entfilename); + ents = (char *) COM_LoadHunkFile (entfilename, &path_id); + } + + if (ents) + { + // use ent file only from the same gamedir as the map + // itself or from a searchpath with higher priority. + if (path_id < loadmodel->path_id) + { + Hunk_FreeToLowMark(mark); + Con_DPrintf("ignored %s from a gamedir with lower priority\n", entfilename); + } + else + { + loadmodel->entities = ents; + Con_DPrintf("Loaded external entity file %s\n", entfilename); + return; + } + } + +_load_embedded: + if (!l->filelen) + { + loadmodel->entities = NULL; + return; + } + loadmodel->entities = (char *) Hunk_AllocName ( l->filelen, loadname); + memcpy (loadmodel->entities, mod_base + l->fileofs, l->filelen); +} + + +/* +================= +Mod_LoadVertexes +================= +*/ +static void Mod_LoadVertexes (lump_t *l) +{ + dvertex_t *in; + mvertex_t *out; + int i, count; + + in = (dvertex_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mvertex_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->vertexes = out; + loadmodel->numvertexes = count; + + for (i=0 ; iposition[0] = LittleFloat (in->point[0]); + out->position[1] = LittleFloat (in->point[1]); + out->position[2] = LittleFloat (in->point[2]); + } +} + +/* +================= +Mod_LoadEdges +================= +*/ +static void Mod_LoadEdges (lump_t *l, int bsp2) +{ + medge_t *out; + int i, count; + + if (bsp2) + { + dledge_t *in = (dledge_t *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadname); + + loadmodel->edges = out; + loadmodel->numedges = count; + + for (i=0 ; iv[0] = LittleLong(in->v[0]); + out->v[1] = LittleLong(in->v[1]); + } + } + else + { + dsedge_t *in = (dsedge_t *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (medge_t *) Hunk_AllocName ( (count + 1) * sizeof(*out), loadname); + + loadmodel->edges = out; + loadmodel->numedges = count; + + for (i=0 ; iv[0] = (unsigned short)LittleShort(in->v[0]); + out->v[1] = (unsigned short)LittleShort(in->v[1]); + } + } +} + +/* +================= +Mod_LoadTexinfo +================= +*/ +static void Mod_LoadTexinfo (lump_t *l) +{ + texinfo_t *in; + mtexinfo_t *out; + int i, j, count, miptex; + int missing = 0; //johnfitz + + in = (texinfo_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mtexinfo_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->texinfo = out; + loadmodel->numtexinfo = count; + + for (i=0 ; ivecs[0][j] = LittleFloat (in->vecs[0][j]); + out->vecs[1][j] = LittleFloat (in->vecs[1][j]); + } + + miptex = LittleLong (in->miptex); + out->flags = LittleLong (in->flags); + + //johnfitz -- rewrote this section + if (miptex >= loadmodel->numtextures-1 || !loadmodel->textures[miptex]) + { + if (out->flags & TEX_SPECIAL) + out->texture = loadmodel->textures[loadmodel->numtextures-1]; + else + out->texture = loadmodel->textures[loadmodel->numtextures-2]; + out->flags |= TEX_MISSING; + missing++; + } + else + { + out->texture = loadmodel->textures[miptex]; + } + //johnfitz + } + + //johnfitz: report missing textures + if (missing && loadmodel->numtextures > 1) + Con_Printf ("Mod_LoadTexinfo: %d texture(s) missing from BSP file\n", missing); + //johnfitz +} + +/* +================ +CalcSurfaceExtents + +Fills in s->texturemins[] and s->extents[] +================ +*/ +static void CalcSurfaceExtents (msurface_t *s) +{ + float mins[2], maxs[2], val; + int i,j, e; + mvertex_t *v; + mtexinfo_t *tex; + int bmins[2], bmaxs[2]; + + mins[0] = mins[1] = FLT_MAX; + maxs[0] = maxs[1] = -FLT_MAX; + + tex = s->texinfo; + + for (i=0 ; inumedges ; i++) + { + e = loadmodel->surfedges[s->firstedge+i]; + if (e >= 0) + v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; + else + v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + + for (j=0 ; j<2 ; j++) + { + /* The following calculation is sensitive to floating-point + * precision. It needs to produce the same result that the + * light compiler does, because R_BuildLightMap uses surf-> + * extents to know the width/height of a surface's lightmap, + * and incorrect rounding here manifests itself as patches + * of "corrupted" looking lightmaps. + * Most light compilers are win32 executables, so they use + * x87 floating point. This means the multiplies and adds + * are done at 80-bit precision, and the result is rounded + * down to 32-bits and stored in val. + * Adding the casts to double seems to be good enough to fix + * lighting glitches when Quakespasm is compiled as x86_64 + * and using SSE2 floating-point. A potential trouble spot + * is the hallway at the beginning of mfxsp17. -- ericw + */ + val = ((double)v->position[0] * (double)tex->vecs[j][0]) + + ((double)v->position[1] * (double)tex->vecs[j][1]) + + ((double)v->position[2] * (double)tex->vecs[j][2]) + + (double)tex->vecs[j][3]; + + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + bmins[i] = floor(mins[i]/16); + bmaxs[i] = ceil(maxs[i]/16); + + s->texturemins[i] = bmins[i] * 16; + s->extents[i] = (bmaxs[i] - bmins[i]) * 16; + + if ( !(tex->flags & TEX_SPECIAL) && s->extents[i] > 2000) //johnfitz -- was 512 in glquake, 256 in winquake + Sys_Error ("Bad surface extents"); + } +} + +/* +================ +Mod_PolyForUnlitSurface -- johnfitz -- creates polys for unlightmapped surfaces (sky and water) + +TODO: merge this into BuildSurfaceDisplayList? +================ +*/ +static void Mod_PolyForUnlitSurface (msurface_t *fa) +{ + const int numverts = fa->numedges; + int i, lindex; + float *vec; + glpoly_t *poly; + float texscale; + + if (fa->flags & (SURF_DRAWTURB | SURF_DRAWSKY)) + texscale = (1.0f/128.0f); //warp animation repeats every 128 + else + texscale = (1.0f/32.0f); //to match r_notexture_mip + + poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (numverts-4) * VERTEXSIZE*sizeof(float)); + poly->next = NULL; + fa->polys = poly; + poly->numverts = numverts; + for (i=0; isurfedges[fa->firstedge + i]; + vec = (lindex > 0) ? + loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position : + loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position; + + VectorCopy (vec, poly->verts[i]); + poly->verts[i][3] = DotProduct(vec, fa->texinfo->vecs[0]) * texscale; + poly->verts[i][4] = DotProduct(vec, fa->texinfo->vecs[1]) * texscale; + } +} + +/* +================= +Mod_CalcSurfaceBounds -- johnfitz -- calculate bounding box for per-surface frustum culling +================= +*/ +static void Mod_CalcSurfaceBounds (msurface_t *s) +{ + int i, e; + mvertex_t *v; + + s->mins[0] = s->mins[1] = s->mins[2] = FLT_MAX; + s->maxs[0] = s->maxs[1] = s->maxs[2] = -FLT_MAX; + + for (i=0 ; inumedges ; i++) + { + e = loadmodel->surfedges[s->firstedge+i]; + if (e >= 0) + v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; + else + v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + + if (s->mins[0] > v->position[0]) + s->mins[0] = v->position[0]; + if (s->mins[1] > v->position[1]) + s->mins[1] = v->position[1]; + if (s->mins[2] > v->position[2]) + s->mins[2] = v->position[2]; + + if (s->maxs[0] < v->position[0]) + s->maxs[0] = v->position[0]; + if (s->maxs[1] < v->position[1]) + s->maxs[1] = v->position[1]; + if (s->maxs[2] < v->position[2]) + s->maxs[2] = v->position[2]; + } +} + +/* +================= +Mod_LoadFaces +================= +*/ +static void Mod_LoadFaces (lump_t *l, qboolean bsp2) +{ + dsface_t *ins; + dlface_t *inl; + msurface_t *out; + int i, count, surfnum, lofs; + int planenum, side, texinfon; + + if (bsp2) + { + ins = NULL; + inl = (dlface_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*inl)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*inl); + } + else + { + ins = (dsface_t *)(mod_base + l->fileofs); + inl = NULL; + if (l->filelen % sizeof(*ins)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*ins); + } + out = (msurface_t *)Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz -- warn mappers about exceeding old limits + if (count > 32767 && !bsp2) + Con_DWarning ("%i faces exceeds standard limit of 32767.\n", count); + //johnfitz + + loadmodel->surfaces = out; + loadmodel->numsurfaces = count; + + for (surfnum=0 ; surfnumfirstedge = LittleLong(inl->firstedge); + out->numedges = LittleLong(inl->numedges); + planenum = LittleLong(inl->planenum); + side = LittleLong(inl->side); + texinfon = LittleLong (inl->texinfo); + for (i=0 ; istyles[i] = inl->styles[i]; + lofs = LittleLong(inl->lightofs); + inl++; + } + else + { + out->firstedge = LittleLong(ins->firstedge); + out->numedges = LittleShort(ins->numedges); + planenum = LittleShort(ins->planenum); + side = LittleShort(ins->side); + texinfon = LittleShort (ins->texinfo); + for (i=0 ; istyles[i] = ins->styles[i]; + lofs = LittleLong(ins->lightofs); + ins++; + } + + out->flags = 0; + if (out->numedges < 3) + Con_Warning("surfnum %d: bad numedges %d\n", surfnum, out->numedges); + + if (side) + out->flags |= SURF_PLANEBACK; + + out->plane = loadmodel->planes + planenum; + + out->texinfo = loadmodel->texinfo + texinfon; + + CalcSurfaceExtents (out); + + Mod_CalcSurfaceBounds (out); //johnfitz -- for per-surface frustum culling + + // lighting info + if (loadmodel->bspversion == BSPVERSION_QUAKE64) + lofs /= 2; // Q64 samples are 16bits instead 8 in normal Quake + + if (lofs == -1) + out->samples = NULL; + else + out->samples = loadmodel->lightdata + (lofs * 3); //johnfitz -- lit support via lordhavoc (was "+ i") + + //johnfitz -- this section rewritten + if (!q_strncasecmp(out->texinfo->texture->name,"sky",3)) // sky surface //also note -- was Q_strncmp, changed to match qbsp + { + out->flags |= (SURF_DRAWSKY | SURF_DRAWTILED); + Mod_PolyForUnlitSurface (out); //no more subdivision + } + else if (out->texinfo->texture->name[0] == '*') // warp surface + { + out->flags |= SURF_DRAWTURB; + if (out->texinfo->flags & TEX_SPECIAL) + out->flags |= SURF_DRAWTILED; + else if (out->samples && !loadmodel->haslitwater) + { + Con_DPrintf ("Map has lit water\n"); + loadmodel->haslitwater = true; + } + + // detect special liquid types + if (!strncmp (out->texinfo->texture->name, "*lava", 5)) + out->flags |= SURF_DRAWLAVA; + else if (!strncmp (out->texinfo->texture->name, "*slime", 6)) + out->flags |= SURF_DRAWSLIME; + else if (!strncmp (out->texinfo->texture->name, "*tele", 5)) + out->flags |= SURF_DRAWTELE; + else out->flags |= SURF_DRAWWATER; + + // polys are only created for unlit water here. + // lit water is handled in BuildSurfaceDisplayList + if (out->flags & SURF_DRAWTILED) + { + Mod_PolyForUnlitSurface (out); + GL_SubdivideSurface (out); + } + } + else if (out->texinfo->texture->name[0] == '{') // ericw -- fence textures + { + out->flags |= SURF_DRAWFENCE; + } + else if (out->texinfo->flags & TEX_MISSING) // texture is missing from bsp + { + if (out->samples) //lightmapped + { + out->flags |= SURF_NOTEXTURE; + } + else // not lightmapped + { + out->flags |= (SURF_NOTEXTURE | SURF_DRAWTILED); + Mod_PolyForUnlitSurface (out); + } + } + //johnfitz + } +} + + +/* +================= +Mod_SetParent +================= +*/ +static void Mod_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents < 0) + return; + Mod_SetParent (node->children[0], node); + Mod_SetParent (node->children[1], node); +} + +/* +================= +Mod_LoadNodes +================= +*/ +static void Mod_LoadNodes_S (lump_t *l) +{ + int i, j, count, p; + dsnode_t *in; + mnode_t *out; + + in = (dsnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz -- warn mappers about exceeding old limits + if (count > 32767) + Con_DWarning ("%i nodes exceeds standard limit of 32767.\n", count); + //johnfitz + + loadmodel->nodes = out; + loadmodel->numnodes = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->planes + p; + + out->firstsurface = (unsigned short)LittleShort (in->firstface); //johnfitz -- explicit cast as unsigned short + out->numsurfaces = (unsigned short)LittleShort (in->numfaces); //johnfitz -- explicit cast as unsigned short + + for (j=0 ; j<2 ; j++) + { + //johnfitz -- hack to handle nodes > 32k, adapted from darkplaces + p = (unsigned short)LittleShort(in->children[j]); + if (p < count) + out->children[j] = loadmodel->nodes + p; + else + { + p = 65535 - p; //note this uses 65535 intentionally, -1 is leaf 0 + if (p < loadmodel->numleafs) + out->children[j] = (mnode_t *)(loadmodel->leafs + p); + else + { + Con_Printf("Mod_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->numleafs); + out->children[j] = (mnode_t *)(loadmodel->leafs); //map it to the solid leaf + } + } + //johnfitz + } + } +} + +static void Mod_LoadNodes_L1 (lump_t *l) +{ + int i, j, count, p; + dl1node_t *in; + mnode_t *out; + + in = (dl1node_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("Mod_LoadNodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (mnode_t *)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->nodes = out; + loadmodel->numnodes = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->planes + p; + + out->firstsurface = LittleLong (in->firstface); //johnfitz -- explicit cast as unsigned short + out->numsurfaces = LittleLong (in->numfaces); //johnfitz -- explicit cast as unsigned short + + for (j=0 ; j<2 ; j++) + { + //johnfitz -- hack to handle nodes > 32k, adapted from darkplaces + p = LittleLong(in->children[j]); + if (p >= 0 && p < count) + out->children[j] = loadmodel->nodes + p; + else + { + p = 0xffffffff - p; //note this uses 65535 intentionally, -1 is leaf 0 + if (p >= 0 && p < loadmodel->numleafs) + out->children[j] = (mnode_t *)(loadmodel->leafs + p); + else + { + Con_Printf("Mod_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->numleafs); + out->children[j] = (mnode_t *)(loadmodel->leafs); //map it to the solid leaf + } + } + //johnfitz + } + } +} + +static void Mod_LoadNodes_L2 (lump_t *l) +{ + int i, j, count, p; + dl2node_t *in; + mnode_t *out; + + in = (dl2node_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("Mod_LoadNodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (mnode_t *)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->nodes = out; + loadmodel->numnodes = count; + + for (i=0 ; iminmaxs[j] = LittleFloat (in->mins[j]); + out->minmaxs[3+j] = LittleFloat (in->maxs[j]); + } + + p = LittleLong(in->planenum); + out->plane = loadmodel->planes + p; + + out->firstsurface = LittleLong (in->firstface); //johnfitz -- explicit cast as unsigned short + out->numsurfaces = LittleLong (in->numfaces); //johnfitz -- explicit cast as unsigned short + + for (j=0 ; j<2 ; j++) + { + //johnfitz -- hack to handle nodes > 32k, adapted from darkplaces + p = LittleLong(in->children[j]); + if (p > 0 && p < count) + out->children[j] = loadmodel->nodes + p; + else + { + p = 0xffffffff - p; //note this uses 65535 intentionally, -1 is leaf 0 + if (p >= 0 && p < loadmodel->numleafs) + out->children[j] = (mnode_t *)(loadmodel->leafs + p); + else + { + Con_Printf("Mod_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->numleafs); + out->children[j] = (mnode_t *)(loadmodel->leafs); //map it to the solid leaf + } + } + //johnfitz + } + } +} + +static void Mod_LoadNodes (lump_t *l, int bsp2) +{ + if (bsp2 == 2) + Mod_LoadNodes_L2(l); + else if (bsp2) + Mod_LoadNodes_L1(l); + else + Mod_LoadNodes_S(l); + + Mod_SetParent (loadmodel->nodes, NULL); // sets nodes and leafs +} + +static void Mod_ProcessLeafs_S (dsleaf_t *in, int filelen) +{ + mleaf_t *out; + int i, j, count, p; + + if (filelen % sizeof(*in)) + Sys_Error ("Mod_ProcessLeafs: funny lump size in %s", loadmodel->name); + count = filelen / sizeof(*in); + out = (mleaf_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz + if (count > 32767) + Host_Error ("Mod_LoadLeafs: %i leafs exceeds limit of 32767.", count); + //johnfitz + + loadmodel->leafs = out; + loadmodel->numleafs = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->contents); + out->contents = p; + + out->firstmarksurface = loadmodel->marksurfaces + (unsigned short)LittleShort(in->firstmarksurface); //johnfitz -- unsigned short + out->nummarksurfaces = (unsigned short)LittleShort(in->nummarksurfaces); //johnfitz -- unsigned short + + p = LittleLong(in->visofs); + if (p == -1) + out->compressed_vis = NULL; + else + out->compressed_vis = loadmodel->visdata + p; + out->efrags = NULL; + + for (j=0 ; j<4 ; j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + //johnfitz -- removed code to mark surfaces as SURF_UNDERWATER + } +} + +static void Mod_ProcessLeafs_L1 (dl1leaf_t *in, int filelen) +{ + mleaf_t *out; + int i, j, count, p; + + if (filelen % sizeof(*in)) + Sys_Error ("Mod_ProcessLeafs: funny lump size in %s", loadmodel->name); + + count = filelen / sizeof(*in); + + out = (mleaf_t *) Hunk_AllocName (count * sizeof(*out), loadname); + + loadmodel->leafs = out; + loadmodel->numleafs = count; + + for (i=0 ; iminmaxs[j] = LittleShort (in->mins[j]); + out->minmaxs[3+j] = LittleShort (in->maxs[j]); + } + + p = LittleLong(in->contents); + out->contents = p; + + out->firstmarksurface = loadmodel->marksurfaces + LittleLong(in->firstmarksurface); //johnfitz -- unsigned short + out->nummarksurfaces = LittleLong(in->nummarksurfaces); //johnfitz -- unsigned short + + p = LittleLong(in->visofs); + if (p == -1) + out->compressed_vis = NULL; + else + out->compressed_vis = loadmodel->visdata + p; + out->efrags = NULL; + + for (j=0 ; j<4 ; j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + //johnfitz -- removed code to mark surfaces as SURF_UNDERWATER + } +} + +static void Mod_ProcessLeafs_L2 (dl2leaf_t *in, int filelen) +{ + mleaf_t *out; + int i, j, count, p; + + if (filelen % sizeof(*in)) + Sys_Error ("Mod_ProcessLeafs: funny lump size in %s", loadmodel->name); + + count = filelen / sizeof(*in); + + out = (mleaf_t *) Hunk_AllocName (count * sizeof(*out), loadname); + + loadmodel->leafs = out; + loadmodel->numleafs = count; + + for (i=0 ; iminmaxs[j] = LittleFloat (in->mins[j]); + out->minmaxs[3+j] = LittleFloat (in->maxs[j]); + } + + p = LittleLong(in->contents); + out->contents = p; + + out->firstmarksurface = loadmodel->marksurfaces + LittleLong(in->firstmarksurface); //johnfitz -- unsigned short + out->nummarksurfaces = LittleLong(in->nummarksurfaces); //johnfitz -- unsigned short + + p = LittleLong(in->visofs); + if (p == -1) + out->compressed_vis = NULL; + else + out->compressed_vis = loadmodel->visdata + p; + out->efrags = NULL; + + for (j=0 ; j<4 ; j++) + out->ambient_sound_level[j] = in->ambient_level[j]; + + //johnfitz -- removed code to mark surfaces as SURF_UNDERWATER + } +} + +/* +================= +Mod_LoadLeafs +================= +*/ +static void Mod_LoadLeafs (lump_t *l, int bsp2) +{ + void *in = (void *)(mod_base + l->fileofs); + + if (bsp2 == 2) + Mod_ProcessLeafs_L2 ((dl2leaf_t *)in, l->filelen); + else if (bsp2) + Mod_ProcessLeafs_L1 ((dl1leaf_t *)in, l->filelen); + else + Mod_ProcessLeafs_S ((dsleaf_t *) in, l->filelen); +} + +/* +================= +Mod_LoadClipnodes +================= +*/ +static void Mod_LoadClipnodes (lump_t *l, qboolean bsp2) +{ + dsclipnode_t *ins; + dlclipnode_t *inl; + + mclipnode_t *out; //johnfitz -- was dclipnode_t + int i, count; + hull_t *hull; + + if (bsp2) + { + ins = NULL; + inl = (dlclipnode_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*inl)) + Sys_Error ("Mod_LoadClipnodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*inl); + } + else + { + ins = (dsclipnode_t *)(mod_base + l->fileofs); + inl = NULL; + if (l->filelen % sizeof(*ins)) + Sys_Error ("Mod_LoadClipnodes: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*ins); + } + out = (mclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + //johnfitz -- warn about exceeding old limits + if (count > 32767 && !bsp2) + Con_DWarning ("%i clipnodes exceeds standard limit of 32767.\n", count); + //johnfitz + + loadmodel->clipnodes = out; + loadmodel->numclipnodes = count; + + hull = &loadmodel->hulls[1]; + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->planes; + hull->clip_mins[0] = -16; + hull->clip_mins[1] = -16; + hull->clip_mins[2] = -24; + hull->clip_maxs[0] = 16; + hull->clip_maxs[1] = 16; + hull->clip_maxs[2] = 32; + + hull = &loadmodel->hulls[2]; + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->planes; + hull->clip_mins[0] = -32; + hull->clip_mins[1] = -32; + hull->clip_mins[2] = -24; + hull->clip_maxs[0] = 32; + hull->clip_maxs[1] = 32; + hull->clip_maxs[2] = 64; + + if (bsp2) + { + for (i=0 ; iplanenum = LittleLong(inl->planenum); + + //johnfitz -- bounds check + if (out->planenum < 0 || out->planenum >= loadmodel->numplanes) + Host_Error ("Mod_LoadClipnodes: planenum out of bounds"); + //johnfitz + + out->children[0] = LittleLong(inl->children[0]); + out->children[1] = LittleLong(inl->children[1]); + //Spike: FIXME: bounds check + } + } + else + { + for (i=0 ; iplanenum = LittleLong(ins->planenum); + + //johnfitz -- bounds check + if (out->planenum < 0 || out->planenum >= loadmodel->numplanes) + Host_Error ("Mod_LoadClipnodes: planenum out of bounds"); + //johnfitz + + //johnfitz -- support clipnodes > 32k + out->children[0] = (unsigned short)LittleShort(ins->children[0]); + out->children[1] = (unsigned short)LittleShort(ins->children[1]); + + if (out->children[0] >= count) + out->children[0] -= 65536; + if (out->children[1] >= count) + out->children[1] -= 65536; + //johnfitz + } + } +} + +/* +================= +Mod_MakeHull0 + +Duplicate the drawing hull structure as a clipping hull +================= +*/ +static void Mod_MakeHull0 (void) +{ + mnode_t *in, *child; + mclipnode_t *out; //johnfitz -- was dclipnode_t + int i, j, count; + hull_t *hull; + + hull = &loadmodel->hulls[0]; + + in = loadmodel->nodes; + count = loadmodel->numnodes; + out = (mclipnode_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + hull->clipnodes = out; + hull->firstclipnode = 0; + hull->lastclipnode = count-1; + hull->planes = loadmodel->planes; + + for (i=0 ; iplanenum = in->plane - loadmodel->planes; + for (j=0 ; j<2 ; j++) + { + child = in->children[j]; + if (child->contents < 0) + out->children[j] = child->contents; + else + out->children[j] = child - loadmodel->nodes; + } + } +} + +/* +================= +Mod_LoadMarksurfaces +================= +*/ +static void Mod_LoadMarksurfaces (lump_t *l, int bsp2) +{ + int i, j, count; + msurface_t **out; + if (bsp2) + { + unsigned int *in = (unsigned int *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (msurface_t **)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->marksurfaces = out; + loadmodel->nummarksurfaces = count; + + for (i=0 ; i= loadmodel->numsurfaces) + Host_Error ("Mod_LoadMarksurfaces: bad surface number"); + out[i] = loadmodel->surfaces + j; + } + } + else + { + short *in = (short *)(mod_base + l->fileofs); + + if (l->filelen % sizeof(*in)) + Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",loadmodel->name); + + count = l->filelen / sizeof(*in); + out = (msurface_t **)Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->marksurfaces = out; + loadmodel->nummarksurfaces = count; + + //johnfitz -- warn mappers about exceeding old limits + if (count > 32767) + Con_DWarning ("%i marksurfaces exceeds standard limit of 32767.\n", count); + //johnfitz + + for (i=0 ; i= loadmodel->numsurfaces) + Sys_Error ("Mod_LoadMarksurfaces: bad surface number"); + out[i] = loadmodel->surfaces + j; + } + } +} + +/* +================= +Mod_LoadSurfedges +================= +*/ +static void Mod_LoadSurfedges (lump_t *l) +{ + int i, count; + int *in, *out; + + in = (int *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (int *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->surfedges = out; + loadmodel->numsurfedges = count; + + for (i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (mplane_t *) Hunk_AllocName ( count*2*sizeof(*out), loadname); + + loadmodel->planes = out; + loadmodel->numplanes = count; + + for (i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) + bits |= 1<dist = LittleFloat (in->dist); + out->type = LittleLong (in->type); + out->signbits = bits; + } +} + +/* +================= +RadiusFromBounds +================= +*/ +static float RadiusFromBounds (vec3_t mins, vec3_t maxs) +{ + int i; + vec3_t corner; + + for (i=0 ; i<3 ; i++) + { + corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); + } + + return VectorLength (corner); +} + +/* +================= +Mod_LoadSubmodels +================= +*/ +static void Mod_LoadSubmodels (lump_t *l) +{ + dmodel_t *in; + dmodel_t *out; + int i, j, count; + + in = (dmodel_t *)(mod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Sys_Error ("MOD_LoadBmodel: funny lump size in %s",loadmodel->name); + count = l->filelen / sizeof(*in); + out = (dmodel_t *) Hunk_AllocName ( count*sizeof(*out), loadname); + + loadmodel->submodels = out; + loadmodel->numsubmodels = count; + + for (i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + out->origin[j] = LittleFloat (in->origin[j]); + } + for (j=0 ; jheadnode[j] = LittleLong (in->headnode[j]); + out->visleafs = LittleLong (in->visleafs); + out->firstface = LittleLong (in->firstface); + out->numfaces = LittleLong (in->numfaces); + } + + // johnfitz -- check world visleafs -- adapted from bjp + out = loadmodel->submodels; + + if (out->visleafs > 8192) + Con_DWarning ("%i visleafs exceeds standard limit of 8192.\n", out->visleafs); + //johnfitz +} + +/* +================= +Mod_BoundsFromClipNode -- johnfitz + +update the model's clipmins and clipmaxs based on each node's plane. + +This works because of the way brushes are expanded in hull generation. +Each brush will include all six axial planes, which bound that brush. +Therefore, the bounding box of the hull can be constructed entirely +from axial planes found in the clipnodes for that hull. +================= +*/ +#if 0 /* disabled for now -- see in Mod_SetupSubmodels() */ +static void Mod_BoundsFromClipNode (qmodel_t *mod, int hull, int nodenum) +{ + mplane_t *plane; + mclipnode_t *node; + + if (nodenum < 0) + return; //hit a leafnode + + node = &mod->clipnodes[nodenum]; + plane = mod->hulls[hull].planes + node->planenum; + switch (plane->type) + { + + case PLANE_X: + if (plane->signbits == 1) + mod->clipmins[0] = q_min(mod->clipmins[0], -plane->dist - mod->hulls[hull].clip_mins[0]); + else + mod->clipmaxs[0] = q_max(mod->clipmaxs[0], plane->dist - mod->hulls[hull].clip_maxs[0]); + break; + case PLANE_Y: + if (plane->signbits == 2) + mod->clipmins[1] = q_min(mod->clipmins[1], -plane->dist - mod->hulls[hull].clip_mins[1]); + else + mod->clipmaxs[1] = q_max(mod->clipmaxs[1], plane->dist - mod->hulls[hull].clip_maxs[1]); + break; + case PLANE_Z: + if (plane->signbits == 4) + mod->clipmins[2] = q_min(mod->clipmins[2], -plane->dist - mod->hulls[hull].clip_mins[2]); + else + mod->clipmaxs[2] = q_max(mod->clipmaxs[2], plane->dist - mod->hulls[hull].clip_maxs[2]); + break; + default: + //skip nonaxial planes; don't need them + break; + } + + Mod_BoundsFromClipNode (mod, hull, node->children[0]); + Mod_BoundsFromClipNode (mod, hull, node->children[1]); +} +#endif /* #if 0 */ + +/* EXTERNAL VIS FILE SUPPORT: + */ +typedef struct vispatch_s +{ + char mapname[32]; + int filelen; // length of data after header (VIS+Leafs) +} vispatch_t; +#define VISPATCH_HEADER_LEN 36 + +static FILE *Mod_FindVisibilityExternal(void) +{ + vispatch_t header; + char visfilename[MAX_QPATH]; + const char* shortname; + unsigned int path_id; + FILE *f; + long pos; + size_t r; + + q_snprintf(visfilename, sizeof(visfilename), "maps/%s.vis", loadname); + if (COM_FOpenFile(visfilename, &f, &path_id) < 0) + { + Con_DPrintf("%s not found, trying ", visfilename); + q_snprintf(visfilename, sizeof(visfilename), "%s.vis", COM_SkipPath(com_gamedir)); + Con_DPrintf("%s\n", visfilename); + if (COM_FOpenFile(visfilename, &f, &path_id) < 0) + { + Con_DPrintf("external vis not found\n"); + return NULL; + } + } + if (path_id < loadmodel->path_id) + { + fclose(f); + Con_DPrintf("ignored %s from a gamedir with lower priority\n", visfilename); + return NULL; + } + + Con_DPrintf("Found external VIS %s\n", visfilename); + + shortname = COM_SkipPath(loadmodel->name); + pos = 0; + while ((r = fread(&header, 1, VISPATCH_HEADER_LEN, f)) == VISPATCH_HEADER_LEN) + { + header.filelen = LittleLong(header.filelen); + if (header.filelen <= 0) { /* bad entry -- don't trust the rest. */ + fclose(f); + return NULL; + } + if (!q_strcasecmp(header.mapname, shortname)) + break; + pos += header.filelen + VISPATCH_HEADER_LEN; + fseek(f, pos, SEEK_SET); + } + if (r != VISPATCH_HEADER_LEN) { + fclose(f); + Con_DPrintf("%s not found in %s\n", shortname, visfilename); + return NULL; + } + + return f; +} + +static byte *Mod_LoadVisibilityExternal(FILE* f) +{ + int filelen; + byte* visdata; + + filelen = 0; + if (!fread(&filelen, 4, 1, f)) return NULL; + filelen = LittleLong(filelen); + if (filelen <= 0) return NULL; + Con_DPrintf("...%d bytes visibility data\n", filelen); + visdata = (byte *) Hunk_AllocName(filelen, "EXT_VIS"); + if (!fread(visdata, filelen, 1, f)) + return NULL; + return visdata; +} + +static void Mod_LoadLeafsExternal(FILE* f) +{ + int filelen; + void* in; + + filelen = 0; + if (!fread(&filelen, 4, 1, f)) return; + filelen = LittleLong(filelen); + if (filelen <= 0) return; + Con_DPrintf("...%d bytes leaf data\n", filelen); + in = Hunk_AllocName(filelen, "EXT_LEAF"); + if (!fread(in, filelen, 1, f)) + return; + Mod_ProcessLeafs_S((dsleaf_t *)in, filelen); +} + +/* +================= +Mod_LoadBrushModel +================= +*/ +static void Mod_LoadBrushModel (qmodel_t *mod, void *buffer) +{ + int i, j; + int bsp2; + dheader_t *header; + dmodel_t *bm; + float radius; //johnfitz + + loadmodel->type = mod_brush; + + header = (dheader_t *)buffer; + + mod->bspversion = LittleLong (header->version); + + switch(mod->bspversion) + { + case BSPVERSION: + bsp2 = false; + break; + case BSP2VERSION_2PSB: + bsp2 = 1; //first iteration + break; + case BSP2VERSION_BSP2: + bsp2 = 2; //sanitised revision + break; + case BSPVERSION_QUAKE64: + bsp2 = false; + break; + default: + Sys_Error ("Mod_LoadBrushModel: %s has unsupported version number (%i)", mod->name, mod->bspversion); + break; + } + +// swap all the lumps + mod_base = (byte *)header; + + for (i = 0; i < (int) sizeof(dheader_t) / 4; i++) + ((int *)header)[i] = LittleLong ( ((int *)header)[i]); + +// load into heap + + Mod_LoadVertexes (&header->lumps[LUMP_VERTEXES]); + Mod_LoadEdges (&header->lumps[LUMP_EDGES], bsp2); + Mod_LoadSurfedges (&header->lumps[LUMP_SURFEDGES]); + Mod_LoadTextures (&header->lumps[LUMP_TEXTURES]); + Mod_LoadLighting (&header->lumps[LUMP_LIGHTING]); + Mod_LoadPlanes (&header->lumps[LUMP_PLANES]); + Mod_LoadTexinfo (&header->lumps[LUMP_TEXINFO]); + Mod_LoadFaces (&header->lumps[LUMP_FACES], bsp2); + Mod_LoadMarksurfaces (&header->lumps[LUMP_MARKSURFACES], bsp2); + + if (mod->bspversion == BSPVERSION && external_vis.value && sv.modelname[0] && !q_strcasecmp(loadname, sv.name)) + { + FILE* fvis; + Con_DPrintf("trying to open external vis file\n"); + fvis = Mod_FindVisibilityExternal(); + if (fvis) { + int mark = Hunk_LowMark(); + loadmodel->leafs = NULL; + loadmodel->numleafs = 0; + Con_DPrintf("found valid external .vis file for map\n"); + loadmodel->visdata = Mod_LoadVisibilityExternal(fvis); + if (loadmodel->visdata) { + Mod_LoadLeafsExternal(fvis); + } + fclose(fvis); + if (loadmodel->visdata && loadmodel->leafs && loadmodel->numleafs) { + goto visdone; + } + Hunk_FreeToLowMark(mark); + Con_DPrintf("External VIS data failed, using standard vis.\n"); + } + } + + Mod_LoadVisibility (&header->lumps[LUMP_VISIBILITY]); + Mod_LoadLeafs (&header->lumps[LUMP_LEAFS], bsp2); +visdone: + Mod_LoadNodes (&header->lumps[LUMP_NODES], bsp2); + Mod_LoadClipnodes (&header->lumps[LUMP_CLIPNODES], bsp2); + Mod_LoadEntities (&header->lumps[LUMP_ENTITIES]); + Mod_LoadSubmodels (&header->lumps[LUMP_MODELS]); + + Mod_MakeHull0 (); + + mod->numframes = 2; // regular and alternate animation + +// +// set up the submodels (FIXME: this is confusing) +// + + // johnfitz -- okay, so that i stop getting confused every time i look at this loop, here's how it works: + // we're looping through the submodels starting at 0. Submodel 0 is the main model, so we don't have to + // worry about clobbering data the first time through, since it's the same data. At the end of the loop, + // we create a new copy of the data to use the next time through. + for (i=0 ; inumsubmodels ; i++) + { + bm = &mod->submodels[i]; + + mod->hulls[0].firstclipnode = bm->headnode[0]; + for (j=1 ; jhulls[j].firstclipnode = bm->headnode[j]; + mod->hulls[j].lastclipnode = mod->numclipnodes-1; + } + + mod->firstmodelsurface = bm->firstface; + mod->nummodelsurfaces = bm->numfaces; + + VectorCopy (bm->maxs, mod->maxs); + VectorCopy (bm->mins, mod->mins); + + //johnfitz -- calculate rotate bounds and yaw bounds + radius = RadiusFromBounds (mod->mins, mod->maxs); + mod->rmaxs[0] = mod->rmaxs[1] = mod->rmaxs[2] = mod->ymaxs[0] = mod->ymaxs[1] = mod->ymaxs[2] = radius; + mod->rmins[0] = mod->rmins[1] = mod->rmins[2] = mod->ymins[0] = mod->ymins[1] = mod->ymins[2] = -radius; + //johnfitz + + //johnfitz -- correct physics cullboxes so that outlying clip brushes on doors and stuff are handled right + if (i > 0 || strcmp(mod->name, sv.modelname) != 0) //skip submodel 0 of sv.worldmodel, which is the actual world + { + // start with the hull0 bounds + VectorCopy (mod->maxs, mod->clipmaxs); + VectorCopy (mod->mins, mod->clipmins); + + // process hull1 (we don't need to process hull2 becuase there's + // no such thing as a brush that appears in hull2 but not hull1) + //Mod_BoundsFromClipNode (mod, 1, mod->hulls[1].firstclipnode); // (disabled for now becuase it fucks up on rotating models) + } + //johnfitz + + mod->numleafs = bm->visleafs; + + if (i < mod->numsubmodels-1) + { // duplicate the basic information + char name[12]; + + sprintf (name, "*%i", i+1); + loadmodel = Mod_FindName (name); + *loadmodel = *mod; + strcpy (loadmodel->name, name); + mod = loadmodel; + } + } +} + +/* +============================================================================== + +ALIAS MODELS + +============================================================================== +*/ + +aliashdr_t *pheader; + +stvert_t stverts[MAXALIASVERTS]; +mtriangle_t triangles[MAXALIASTRIS]; + +// a pose is a single set of vertexes. a frame may be +// an animating sequence of poses +trivertx_t *poseverts[MAXALIASFRAMES]; +static int posenum; + +/* +================= +Mod_LoadAliasFrame +================= +*/ +static void *Mod_LoadAliasFrame (void * pin, maliasframedesc_t *frame) +{ + trivertx_t *pinframe; + int i; + daliasframe_t *pdaliasframe; + + if (posenum >= MAXALIASFRAMES) + Sys_Error ("posenum >= MAXALIASFRAMES"); + + pdaliasframe = (daliasframe_t *)pin; + + q_strlcpy (frame->name, pdaliasframe->name, sizeof (frame->name)); + frame->firstpose = posenum; + frame->numposes = 1; + + for (i=0 ; i<3 ; i++) + { + // these are byte values, so we don't have to worry about + // endianness + frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; + frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; + } + + pinframe = (trivertx_t *)(pdaliasframe + 1); + + poseverts[posenum] = pinframe; + posenum++; + + pinframe += pheader->numverts; + + return (void *)pinframe; +} + + +/* +================= +Mod_LoadAliasGroup +================= +*/ +static void *Mod_LoadAliasGroup (void * pin, maliasframedesc_t *frame) +{ + daliasgroup_t *pingroup; + int i, numframes; + daliasinterval_t *pin_intervals; + void *ptemp; + + pingroup = (daliasgroup_t *)pin; + + numframes = LittleLong (pingroup->numframes); + + frame->firstpose = posenum; + frame->numposes = numframes; + + for (i=0 ; i<3 ; i++) + { + // these are byte values, so we don't have to worry about endianness + frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; + frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; + } + + pin_intervals = (daliasinterval_t *)(pingroup + 1); + + frame->interval = LittleFloat (pin_intervals->interval); + + pin_intervals += numframes; + + ptemp = (void *)pin_intervals; + + for (i=0 ; i= MAXALIASFRAMES) Sys_Error ("posenum >= MAXALIASFRAMES"); + + poseverts[posenum] = (trivertx_t *)((daliasframe_t *)ptemp + 1); + posenum++; + + ptemp = (trivertx_t *)((daliasframe_t *)ptemp + 1) + pheader->numverts; + } + + return ptemp; +} + +//========================================================= + + +/* +================= +Mod_FloodFillSkin + +Fill background pixels so mipmapping doesn't have haloes - Ed +================= +*/ + +typedef struct +{ + short x, y; +} floodfill_t; + +// must be a power of 2 +#define FLOODFILL_FIFO_SIZE 0x1000 +#define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1) + +#define FLOODFILL_STEP( off, dx, dy ) \ +do { \ + if (pos[off] == fillcolor) \ + { \ + pos[off] = 255; \ + fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \ + inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \ + } \ + else if (pos[off] != 255) fdc = pos[off]; \ +} while (0) + +static void Mod_FloodFillSkin( byte *skin, int skinwidth, int skinheight ) +{ + byte fillcolor = *skin; // assume this is the pixel to fill + floodfill_t fifo[FLOODFILL_FIFO_SIZE]; + int inpt = 0, outpt = 0; + int filledcolor = -1; + int i; + + if (filledcolor == -1) + { + filledcolor = 0; + // attempt to find opaque black + for (i = 0; i < 256; ++i) + { + if (d_8to24table[i] == (255 << 0)) // alpha 1.0 + { + filledcolor = i; + break; + } + } + } + + // can't fill to filled color or to transparent color (used as visited marker) + if ((fillcolor == filledcolor) || (fillcolor == 255)) + { + //printf( "not filling skin from %d to %d\n", fillcolor, filledcolor ); + return; + } + + fifo[inpt].x = 0, fifo[inpt].y = 0; + inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; + + while (outpt != inpt) + { + int x = fifo[outpt].x, y = fifo[outpt].y; + int fdc = filledcolor; + byte *pos = &skin[x + skinwidth * y]; + + outpt = (outpt + 1) & FLOODFILL_FIFO_MASK; + + if (x > 0) FLOODFILL_STEP( -1, -1, 0 ); + if (x < skinwidth - 1) FLOODFILL_STEP( 1, 1, 0 ); + if (y > 0) FLOODFILL_STEP( -skinwidth, 0, -1 ); + if (y < skinheight - 1) FLOODFILL_STEP( skinwidth, 0, 1 ); + skin[x + skinwidth * y] = fdc; + } +} + +/* +=============== +Mod_LoadAllSkins +=============== +*/ +static void *Mod_LoadAllSkins (int numskins, daliasskintype_t *pskintype) +{ + int i, j, k, size, groupskins; + char name[MAX_QPATH]; + byte *skin, *texels; + daliasskingroup_t *pinskingroup; + daliasskininterval_t *pinskinintervals; + char fbr_mask_name[MAX_QPATH]; //johnfitz -- added for fullbright support + src_offset_t offset; //johnfitz + unsigned int texflags = TEXPREF_PAD; + + skin = (byte *)(pskintype + 1); + + if (numskins < 1 || numskins > MAX_SKINS) + Sys_Error ("Mod_LoadAliasModel: Invalid # of skins: %d", numskins); + + size = pheader->skinwidth * pheader->skinheight; + + if (loadmodel->flags & MF_HOLEY) + texflags |= TEXPREF_ALPHA; + + for (i=0 ; itype == ALIAS_SKIN_SINGLE) + { + Mod_FloodFillSkin( skin, pheader->skinwidth, pheader->skinheight ); + + // save 8 bit texels for the player model to remap + texels = (byte *) Hunk_AllocName(size, loadname); + pheader->texels[i] = texels - (byte *)pheader; + memcpy (texels, (byte *)(pskintype + 1), size); + + //johnfitz -- rewritten + q_snprintf (name, sizeof(name), "%s:frame%i", loadmodel->name, i); + offset = (src_offset_t)(pskintype+1) - (src_offset_t)mod_base; + if (Mod_CheckFullbrights ((byte *)(pskintype+1), size)) + { + pheader->gltextures[i][0] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype+1), loadmodel->name, offset, texflags | TEXPREF_NOBRIGHT); + q_snprintf (fbr_mask_name, sizeof(fbr_mask_name), "%s:frame%i_glow", loadmodel->name, i); + pheader->fbtextures[i][0] = TexMgr_LoadImage (loadmodel, fbr_mask_name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype+1), loadmodel->name, offset, texflags | TEXPREF_FULLBRIGHT); + } + else + { + pheader->gltextures[i][0] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype+1), loadmodel->name, offset, texflags); + pheader->fbtextures[i][0] = NULL; + } + + pheader->gltextures[i][3] = pheader->gltextures[i][2] = pheader->gltextures[i][1] = pheader->gltextures[i][0]; + pheader->fbtextures[i][3] = pheader->fbtextures[i][2] = pheader->fbtextures[i][1] = pheader->fbtextures[i][0]; + //johnfitz + + pskintype = (daliasskintype_t *)((byte *)(pskintype+1) + size); + } + else + { + // animating skin group. yuck. + pskintype++; + pinskingroup = (daliasskingroup_t *)pskintype; + groupskins = LittleLong (pinskingroup->numskins); + pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); + + pskintype = (daliasskintype_t *)(pinskinintervals + groupskins); + + for (j=0 ; jskinwidth, pheader->skinheight ); + if (j == 0) { + texels = (byte *) Hunk_AllocName(size, loadname); + pheader->texels[i] = texels - (byte *)pheader; + memcpy (texels, (byte *)(pskintype), size); + } + + //johnfitz -- rewritten + q_snprintf (name, sizeof(name), "%s:frame%i_%i", loadmodel->name, i,j); + offset = (src_offset_t)(pskintype) - (src_offset_t)mod_base; //johnfitz + if (Mod_CheckFullbrights ((byte *)(pskintype), size)) + { + pheader->gltextures[i][j&3] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype), loadmodel->name, offset, texflags | TEXPREF_NOBRIGHT); + q_snprintf (fbr_mask_name, sizeof(fbr_mask_name), "%s:frame%i_%i_glow", loadmodel->name, i,j); + pheader->fbtextures[i][j&3] = TexMgr_LoadImage (loadmodel, fbr_mask_name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype), loadmodel->name, offset, texflags | TEXPREF_FULLBRIGHT); + } + else + { + pheader->gltextures[i][j&3] = TexMgr_LoadImage (loadmodel, name, pheader->skinwidth, pheader->skinheight, + SRC_INDEXED, (byte *)(pskintype), loadmodel->name, offset, texflags); + pheader->fbtextures[i][j&3] = NULL; + } + //johnfitz + + pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); + } + k = j; + for (/**/; j < 4; j++) + pheader->gltextures[i][j&3] = pheader->gltextures[i][j - k]; + } + } + + return (void *)pskintype; +} + +//========================================================================= + +/* +================= +Mod_CalcAliasBounds -- johnfitz -- calculate bounds of alias model for nonrotated, yawrotated, and fullrotated cases +================= +*/ +static void Mod_CalcAliasBounds (aliashdr_t *a) +{ + int i,j,k; + float dist, yawradius, radius; + vec3_t v; + + //clear out all data + for (i=0; i<3;i++) + { + loadmodel->mins[i] = loadmodel->ymins[i] = loadmodel->rmins[i] = FLT_MAX; + loadmodel->maxs[i] = loadmodel->ymaxs[i] = loadmodel->rmaxs[i] = -FLT_MAX; + radius = yawradius = 0; + } + + //process verts + for (i=0 ; inumposes; i++) + for (j=0; jnumverts; j++) + { + for (k=0; k<3;k++) + v[k] = poseverts[i][j].v[k] * pheader->scale[k] + pheader->scale_origin[k]; + + for (k=0; k<3;k++) + { + loadmodel->mins[k] = q_min(loadmodel->mins[k], v[k]); + loadmodel->maxs[k] = q_max(loadmodel->maxs[k], v[k]); + } + dist = v[0] * v[0] + v[1] * v[1]; + if (yawradius < dist) + yawradius = dist; + dist += v[2] * v[2]; + if (radius < dist) + radius = dist; + } + + //rbounds will be used when entity has nonzero pitch or roll + radius = sqrt(radius); + loadmodel->rmins[0] = loadmodel->rmins[1] = loadmodel->rmins[2] = -radius; + loadmodel->rmaxs[0] = loadmodel->rmaxs[1] = loadmodel->rmaxs[2] = radius; + + //ybounds will be used when entity has nonzero yaw + yawradius = sqrt(yawradius); + loadmodel->ymins[0] = loadmodel->ymins[1] = -yawradius; + loadmodel->ymaxs[0] = loadmodel->ymaxs[1] = yawradius; + loadmodel->ymins[2] = loadmodel->mins[2]; + loadmodel->ymaxs[2] = loadmodel->maxs[2]; +} + +static qboolean +nameInList(const char *list, const char *name) +{ + const char *s; + char tmp[MAX_QPATH]; + int i; + + s = list; + + while (*s) + { + // make a copy until the next comma or end of string + i = 0; + while (*s && *s != ',') + { + if (i < MAX_QPATH - 1) + tmp[i++] = *s; + s++; + } + tmp[i] = '\0'; + //compare it to the model name + if (!strcmp(name, tmp)) + { + return true; + } + //search forwards to the next comma or end of string + while (*s && *s == ',') + s++; + } + return false; +} + +/* +================= +Mod_SetExtraFlags -- johnfitz -- set up extra flags that aren't in the mdl +================= +*/ +void Mod_SetExtraFlags (qmodel_t *mod) +{ + extern cvar_t r_nolerp_list, r_noshadow_list; + + if (!mod || mod->type != mod_alias) + return; + + mod->flags &= (0xFF | MF_HOLEY); //only preserve first byte, plus MF_HOLEY + + // nolerp flag + if (nameInList(r_nolerp_list.string, mod->name)) + mod->flags |= MOD_NOLERP; + + // noshadow flag + if (nameInList(r_noshadow_list.string, mod->name)) + mod->flags |= MOD_NOSHADOW; + + // fullbright hack (TODO: make this a cvar list) + if (!strcmp (mod->name, "progs/flame2.mdl") || + !strcmp (mod->name, "progs/flame.mdl") || + !strcmp (mod->name, "progs/boss.mdl")) + { + mod->flags |= MOD_FBRIGHTHACK; + } +} + +/* +================= +Mod_LoadAliasModel +================= +*/ +static void Mod_LoadAliasModel (qmodel_t *mod, void *buffer) +{ + int i, j; + mdl_t *pinmodel; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + int version, numframes; + int size; + daliasframetype_t *pframetype; + daliasskintype_t *pskintype; + int start, end, total; + + start = Hunk_LowMark (); + + pinmodel = (mdl_t *)buffer; + mod_base = (byte *)buffer; //johnfitz + + version = LittleLong (pinmodel->version); + if (version != ALIAS_VERSION) + Sys_Error ("%s has wrong version number (%i should be %i)", + mod->name, version, ALIAS_VERSION); + +// +// allocate space for a working header, plus all the data except the frames, +// skin and group info +// + size = sizeof(aliashdr_t) + + (LittleLong (pinmodel->numframes) - 1) * sizeof (pheader->frames[0]); + pheader = (aliashdr_t *) Hunk_AllocName (size, loadname); + + mod->flags = LittleLong (pinmodel->flags); + +// +// endian-adjust and copy the data, starting with the alias model header +// + pheader->boundingradius = LittleFloat (pinmodel->boundingradius); + pheader->numskins = LittleLong (pinmodel->numskins); + pheader->skinwidth = LittleLong (pinmodel->skinwidth); + pheader->skinheight = LittleLong (pinmodel->skinheight); + + if (pheader->skinheight > MAX_LBM_HEIGHT) + Con_DWarning ("model %s has a skin taller than %d", mod->name, + MAX_LBM_HEIGHT); + + pheader->numverts = LittleLong (pinmodel->numverts); + + if (pheader->numverts <= 0) + Sys_Error ("model %s has no vertices", mod->name); + + if (pheader->numverts > MAXALIASVERTS) + Sys_Error ("model %s has too many vertices (%d; max = %d)", mod->name, pheader->numverts, MAXALIASVERTS); + + pheader->numtris = LittleLong (pinmodel->numtris); + + if (pheader->numtris <= 0) + Sys_Error ("model %s has no triangles", mod->name); + + if (pheader->numtris > MAXALIASTRIS) + Sys_Error ("model %s has too many triangles (%d; max = %d)", mod->name, pheader->numtris, MAXALIASTRIS); + + pheader->numframes = LittleLong (pinmodel->numframes); + numframes = pheader->numframes; + if (numframes < 1) + Sys_Error ("Mod_LoadAliasModel: Invalid # of frames: %d", numframes); + + pheader->size = LittleFloat (pinmodel->size) * ALIAS_BASE_SIZE_RATIO; + mod->synctype = (synctype_t) LittleLong (pinmodel->synctype); + mod->numframes = pheader->numframes; + + for (i=0 ; i<3 ; i++) + { + pheader->scale[i] = LittleFloat (pinmodel->scale[i]); + pheader->scale_origin[i] = LittleFloat (pinmodel->scale_origin[i]); + pheader->eyeposition[i] = LittleFloat (pinmodel->eyeposition[i]); + } + +// +// load the skins +// + pskintype = (daliasskintype_t *)&pinmodel[1]; + pskintype = (daliasskintype_t *) Mod_LoadAllSkins (pheader->numskins, pskintype); + +// +// load base s and t vertices +// + pinstverts = (stvert_t *)pskintype; + + for (i=0 ; inumverts ; i++) + { + stverts[i].onseam = LittleLong (pinstverts[i].onseam); + stverts[i].s = LittleLong (pinstverts[i].s); + stverts[i].t = LittleLong (pinstverts[i].t); + } + +// +// load triangle lists +// + pintriangles = (dtriangle_t *)&pinstverts[pheader->numverts]; + + for (i=0 ; inumtris ; i++) + { + triangles[i].facesfront = LittleLong (pintriangles[i].facesfront); + + for (j=0 ; j<3 ; j++) + { + triangles[i].vertindex[j] = + LittleLong (pintriangles[i].vertindex[j]); + } + } + +// +// load the frames +// + posenum = 0; + pframetype = (daliasframetype_t *)&pintriangles[pheader->numtris]; + + for (i=0 ; itype); + if (frametype == ALIAS_SINGLE) + pframetype = (daliasframetype_t *) Mod_LoadAliasFrame (pframetype + 1, &pheader->frames[i]); + else + pframetype = (daliasframetype_t *) Mod_LoadAliasGroup (pframetype + 1, &pheader->frames[i]); + } + + pheader->numposes = posenum; + + mod->type = mod_alias; + + Mod_SetExtraFlags (mod); //johnfitz + + Mod_CalcAliasBounds (pheader); //johnfitz + + // + // build the draw lists + // + GL_MakeAliasModelDisplayLists (mod, pheader); + +// +// move the complete, relocatable alias model to the cache +// + end = Hunk_LowMark (); + total = end - start; + + Cache_Alloc (&mod->cache, total, loadname); + if (!mod->cache.data) + return; + memcpy (mod->cache.data, pheader, total); + + Hunk_FreeToLowMark (start); +} + +//============================================================================= + +/* +================= +Mod_LoadSpriteFrame +================= +*/ +static void *Mod_LoadSpriteFrame (void * pin, mspriteframe_t **ppframe, int framenum) +{ + dspriteframe_t *pinframe; + mspriteframe_t *pspriteframe; + int width, height, size, origin[2]; + char name[64]; + src_offset_t offset; //johnfitz + + pinframe = (dspriteframe_t *)pin; + + width = LittleLong (pinframe->width); + height = LittleLong (pinframe->height); + size = width * height; + + pspriteframe = (mspriteframe_t *) Hunk_AllocName (sizeof (mspriteframe_t),loadname); + *ppframe = pspriteframe; + + pspriteframe->width = width; + pspriteframe->height = height; + origin[0] = LittleLong (pinframe->origin[0]); + origin[1] = LittleLong (pinframe->origin[1]); + + pspriteframe->up = origin[1]; + pspriteframe->down = origin[1] - height; + pspriteframe->left = origin[0]; + pspriteframe->right = width + origin[0]; + + //johnfitz -- image might be padded + pspriteframe->smax = (float)width/(float)TexMgr_PadConditional(width); + pspriteframe->tmax = (float)height/(float)TexMgr_PadConditional(height); + //johnfitz + + q_snprintf (name, sizeof(name), "%s:frame%i", loadmodel->name, framenum); + offset = (src_offset_t)(pinframe+1) - (src_offset_t)mod_base; //johnfitz + pspriteframe->gltexture = + TexMgr_LoadImage (loadmodel, name, width, height, SRC_INDEXED, + (byte *)(pinframe + 1), loadmodel->name, offset, + TEXPREF_PAD | TEXPREF_ALPHA | TEXPREF_NOPICMIP); //johnfitz -- TexMgr + + return (void *)((byte *)pinframe + sizeof (dspriteframe_t) + size); +} + + +/* +================= +Mod_LoadSpriteGroup +================= +*/ +static void *Mod_LoadSpriteGroup (void * pin, mspriteframe_t **ppframe, int framenum, spriteframetype_t type) +{ + dspritegroup_t *pingroup; + mspritegroup_t *pspritegroup; + int i, numframes; + dspriteinterval_t *pin_intervals; + float *poutintervals; + void *ptemp; + + pingroup = (dspritegroup_t *)pin; + + numframes = LittleLong (pingroup->numframes); + if (type == SPR_ANGLED && numframes != 8) + Sys_Error ("Mod_LoadSpriteGroup: Bad # of frames: %d", numframes); + + pspritegroup = (mspritegroup_t *) Hunk_AllocName (sizeof (mspritegroup_t) + + (numframes - 1) * sizeof (pspritegroup->frames[0]), loadname); + + pspritegroup->numframes = numframes; + + *ppframe = (mspriteframe_t *)pspritegroup; + + pin_intervals = (dspriteinterval_t *)(pingroup + 1); + + poutintervals = (float *) Hunk_AllocName (numframes * sizeof (float), loadname); + + pspritegroup->intervals = poutintervals; + + for (i=0 ; iinterval); + if (*poutintervals <= 0.0) + Sys_Error ("Mod_LoadSpriteGroup: interval<=0"); + + poutintervals++; + pin_intervals++; + } + + ptemp = (void *)pin_intervals; + + for (i=0 ; iframes[i], framenum * 100 + i); + } + + return ptemp; +} + + +/* +================= +Mod_LoadSpriteModel +================= +*/ +static void Mod_LoadSpriteModel (qmodel_t *mod, void *buffer) +{ + int i; + int version; + dsprite_t *pin; + msprite_t *psprite; + int numframes; + int size; + dspriteframetype_t *pframetype; + + pin = (dsprite_t *)buffer; + mod_base = (byte *)buffer; //johnfitz + + version = LittleLong (pin->version); + if (version != SPRITE_VERSION) + Sys_Error ("%s has wrong version number " + "(%i should be %i)", mod->name, version, SPRITE_VERSION); + + numframes = LittleLong (pin->numframes); + + size = sizeof (msprite_t) + (numframes - 1) * sizeof (psprite->frames); + + psprite = (msprite_t *) Hunk_AllocName (size, loadname); + + mod->cache.data = psprite; + + psprite->type = LittleLong (pin->type); + psprite->maxwidth = LittleLong (pin->width); + psprite->maxheight = LittleLong (pin->height); + psprite->beamlength = LittleFloat (pin->beamlength); + mod->synctype = (synctype_t) LittleLong (pin->synctype); + psprite->numframes = numframes; + + mod->mins[0] = mod->mins[1] = -psprite->maxwidth/2; + mod->maxs[0] = mod->maxs[1] = psprite->maxwidth/2; + mod->mins[2] = -psprite->maxheight/2; + mod->maxs[2] = psprite->maxheight/2; + +// +// load the frames +// + if (numframes < 1) + Sys_Error ("Mod_LoadSpriteModel: Invalid # of frames: %d", numframes); + + mod->numframes = numframes; + + pframetype = (dspriteframetype_t *)(pin + 1); + + for (i=0 ; itype); + psprite->frames[i].type = frametype; + + if (frametype == SPR_SINGLE) + { + pframetype = (dspriteframetype_t *) + Mod_LoadSpriteFrame (pframetype + 1, &psprite->frames[i].frameptr, i); + } + else + { + pframetype = (dspriteframetype_t *) + Mod_LoadSpriteGroup (pframetype + 1, &psprite->frames[i].frameptr, i, frametype); + } + } + + mod->type = mod_sprite; +} + +//============================================================================= + +/* +================ +Mod_Print +================ +*/ +static void Mod_Print (void) +{ + int i; + qmodel_t *mod; + + Con_SafePrintf ("Cached models:\n"); //johnfitz -- safeprint instead of print + for (i=0, mod=mod_known ; i < mod_numknown ; i++, mod++) + { + Con_SafePrintf ("%8p : %s\n", mod->cache.data, mod->name); //johnfitz -- safeprint instead of print + } + Con_Printf ("%i models\n",mod_numknown); //johnfitz -- print the total too +} + diff --git a/Quake/gl_model.hpp b/Quake/gl_model.hpp new file mode 100644 index 0000000..aca75cc --- /dev/null +++ b/Quake/gl_model.hpp @@ -0,0 +1,520 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef GL_MODEL_H +#define GL_MODEL_H + +#include "modelgen.hpp" +#include "spritegn.hpp" + +/* + +d*_t structures are on-disk representations +m*_t structures are in-memory + +*/ + +// entity effects + +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 +#define EF_QEX_QUADLIGHT 16 // 2021 rerelease +#define EF_QEX_PENTALIGHT 32 // 2021 rerelease +#define EF_QEX_CANDLELIGHT 64 // 2021 rerelease + + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + vec3_t position; +} mvertex_t; + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct mplane_s +{ + vec3_t normal; + float dist; + byte type; // for texture axis selection and fast side tests + byte signbits; // signx + signy<<1 + signz<<1 + byte pad[2]; +} mplane_t; + +// ericw -- each texture has two chains, so we can clear the model chains +// without affecting the world +typedef enum { + chain_world = 0, + chain_model = 1 +} texchain_t; + +typedef struct texture_s +{ + char name[16]; + unsigned width, height; + unsigned shift; // Q64 + struct gltexture_s *gltexture; //johnfitz -- pointer to gltexture + struct gltexture_s *fullbright; //johnfitz -- fullbright mask texture + struct gltexture_s *warpimage; //johnfitz -- for water animation + qboolean update_warp; //johnfitz -- update warp this frame + struct msurface_s *texturechains[2]; // for texture chains + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frmae 1 use these +} texture_t; + + +#define SURF_PLANEBACK 2 +#define SURF_DRAWSKY 4 +#define SURF_DRAWSPRITE 8 +#define SURF_DRAWTURB 0x10 +#define SURF_DRAWTILED 0x20 +#define SURF_DRAWBACKGROUND 0x40 +#define SURF_UNDERWATER 0x80 +#define SURF_NOTEXTURE 0x100 //johnfitz +#define SURF_DRAWFENCE 0x200 +#define SURF_DRAWLAVA 0x400 +#define SURF_DRAWSLIME 0x800 +#define SURF_DRAWTELE 0x1000 +#define SURF_DRAWWATER 0x2000 + +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + unsigned int v[2]; + unsigned int cachededgeoffset; +} medge_t; + +typedef struct +{ + float vecs[2][4]; + texture_t *texture; + int flags; +} mtexinfo_t; + +#define VERTEXSIZE 7 + +typedef struct glpoly_s +{ + struct glpoly_s *next; + struct glpoly_s *chain; + int numverts; + float verts[4][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) +} glpoly_t; + +typedef struct msurface_s +{ + int visframe; // should be drawn when node is crossed + float mins[3]; // johnfitz -- for frustum culling + float maxs[3]; // johnfitz -- for frustum culling + + mplane_t *plane; + int flags; + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + + short texturemins[2]; + short extents[2]; + + int light_s, light_t; // gl lightmap coordinates + + glpoly_t *polys; // multiple if warped + struct msurface_s *texturechain; + + mtexinfo_t *texinfo; + + int vbo_firstvert; // index of this surface's first vert in the VBO + +// lighting info + int dlightframe; + unsigned int dlightbits[(MAX_DLIGHTS + 31) >> 5]; + // int is 32 bits, need an array for MAX_DLIGHTS > 32 + + int lightmaptexturenum; + byte styles[MAXLIGHTMAPS]; + int cached_light[MAXLIGHTMAPS]; // values currently used in lightmap + qboolean cached_dlight; // true if dynamic light in cache + byte *samples; // [numstyles*surfsize] +} msurface_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned int firstsurface; + unsigned int numsurfaces; +} mnode_t; + + + +typedef struct mleaf_s +{ +// common with node + int contents; // wil be a negative contents number + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; + +// leaf specific + byte *compressed_vis; + efrag_t *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int key; // BSP sequence number for leaf's contents + byte ambient_sound_level[NUM_AMBIENTS]; +} mleaf_t; + +//johnfitz -- for clipnodes>32k +typedef struct mclipnode_s +{ + int planenum; + int children[2]; // negative numbers are contents +} mclipnode_t; +//johnfitz + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + mclipnode_t *clipnodes; //johnfitz -- was dclipnode_t + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +/* +============================================================================== + +SPRITE MODELS + +============================================================================== +*/ + + +// FIXME: shorten these? +typedef struct mspriteframe_s +{ + int width, height; + float up, down, left, right; + float smax, tmax; //johnfitz -- image might be padded + struct gltexture_s *gltexture; +} mspriteframe_t; + +typedef struct +{ + int numframes; + float *intervals; + mspriteframe_t *frames[1]; +} mspritegroup_t; + +typedef struct +{ + spriteframetype_t type; + mspriteframe_t *frameptr; +} mspriteframedesc_t; + +typedef struct +{ + int type; + int maxwidth; + int maxheight; + int numframes; + float beamlength; // remove? + void *cachespot; // remove? + mspriteframedesc_t frames[1]; +} msprite_t; + + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +//-- from RMQEngine +// split out to keep vertex sizes down +typedef struct aliasmesh_s +{ + float st[2]; + unsigned short vertindex; +} aliasmesh_t; + +typedef struct meshxyz_s +{ + byte xyz[4]; + signed char normal[4]; +} meshxyz_t; + +typedef struct meshst_s +{ + float st[2]; +} meshst_t; +//-- + +typedef struct +{ + int firstpose; + int numposes; + float interval; + trivertx_t bboxmin; + trivertx_t bboxmax; + int frame; + char name[16]; +} maliasframedesc_t; + +typedef struct +{ + trivertx_t bboxmin; + trivertx_t bboxmax; + int frame; +} maliasgroupframedesc_t; + +typedef struct +{ + int numframes; + int intervals; + maliasgroupframedesc_t frames[1]; +} maliasgroup_t; + +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct mtriangle_s { + int facesfront; + int vertindex[3]; +} mtriangle_t; + + +#define MAX_SKINS 32 +typedef struct { + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; + + //ericw -- used to populate vbo + int numverts_vbo; // number of verts with unique x,y,z,s,t + intptr_t meshdesc; // offset into extradata: numverts_vbo aliasmesh_t + int numindexes; + intptr_t indexes; // offset into extradata: numindexes unsigned shorts + intptr_t vertexes; // offset into extradata: numposes*vertsperframe trivertx_t + //ericw -- + + int numposes; + int poseverts; + int posedata; // numposes*poseverts trivert_t + int commands; // gl command list with embedded s/t + struct gltexture_s *gltextures[MAX_SKINS][4]; //johnfitz + struct gltexture_s *fbtextures[MAX_SKINS][4]; //johnfitz + int texels[MAX_SKINS]; // only for player skins + maliasframedesc_t frames[1]; // variable sized +} aliashdr_t; + +#define MAXALIASVERTS 2000 //johnfitz -- was 1024 +#define MAXALIASFRAMES 1024 //spike -- was 256 +#define MAXALIASTRIS 4096 //ericw -- was 2048 +extern aliashdr_t *pheader; +extern stvert_t stverts[MAXALIASVERTS]; +extern mtriangle_t triangles[MAXALIASTRIS]; +extern trivertx_t *poseverts[MAXALIASFRAMES]; + +//=================================================================== + +// +// Whole model +// + +typedef enum {mod_brush, mod_sprite, mod_alias} modtype_t; + +#define EF_ROCKET 1 // leave a trail +#define EF_GRENADE 2 // leave a trail +#define EF_GIB 4 // leave a trail +#define EF_ROTATE 8 // rotate (bonus items) +#define EF_TRACER 16 // green split trail +#define EF_ZOMGIB 32 // small blood trail +#define EF_TRACER2 64 // orange split trail + rotate +#define EF_TRACER3 128 // purple trail +#define MF_HOLEY (1u<<14) // MarkV/QSS -- make index 255 transparent on mdl's + +//johnfitz -- extra flags for rendering +#define MOD_NOLERP 256 //don't lerp when animating +#define MOD_NOSHADOW 512 //don't cast a shadow +#define MOD_FBRIGHTHACK 1024 //when fullbrights are disabled, use a hack to render this model brighter +//johnfitz + +typedef struct qmodel_s +{ + char name[MAX_QPATH]; + unsigned int path_id; // path id of the game directory + // that this model came from + qboolean needload; // bmodels and sprites don't cache normally + + modtype_t type; + int numframes; + synctype_t synctype; + + int flags; + +// +// volume occupied by the model graphics +// + vec3_t mins, maxs; + vec3_t ymins, ymaxs; //johnfitz -- bounds for entities with nonzero yaw + vec3_t rmins, rmaxs; //johnfitz -- bounds for entities with nonzero pitch or roll + //johnfitz -- removed float radius; + +// +// solid volume for clipping +// + qboolean clipbox; + vec3_t clipmins, clipmaxs; + +// +// brush model +// + int firstmodelsurface, nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + mleaf_t *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + mclipnode_t *clipnodes; //johnfitz -- was dclipnode_t + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + byte *lightdata; + char *entities; + + qboolean viswarn; // for Mod_DecompressVis() + + int bspversion; + qboolean haslitwater; +// +// alias model +// + + GLuint meshvbo; + GLuint meshindexesvbo; + int vboindexofs; // offset in vbo of the hdr->numindexes unsigned shorts + int vboxyzofs; // offset in vbo of hdr->numposes*hdr->numverts_vbo meshxyz_t + int vbostofs; // offset in vbo of hdr->numverts_vbo meshst_t + +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata + +} qmodel_t; + +//============================================================================ + +void Mod_Init (void); +void Mod_ClearAll (void); +void Mod_ResetAll (void); // for gamedir changes (Host_Game_f) +qmodel_t *Mod_ForName (const char *name, qboolean crash); +void *Mod_Extradata (qmodel_t *mod); // handles caching +void Mod_TouchModel (const char *name); + +mleaf_t *Mod_PointInLeaf (vec3_t p, qmodel_t *model); +byte *Mod_LeafPVS (mleaf_t *leaf, qmodel_t *model); +byte *Mod_NoVisPVS (qmodel_t *model); + +void Mod_SetExtraFlags (qmodel_t *mod); + +#endif /* GL_MODEL_H */ diff --git a/Quake/gl_refrag.cpp b/Quake/gl_refrag.cpp new file mode 100644 index 0000000..6047ee5 --- /dev/null +++ b/Quake/gl_refrag.cpp @@ -0,0 +1,221 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_efrag.c + +#include "quakedef.hpp" + +static mnode_t *r_pefragtopnode; + + +/* +=============================================================================== + + ENTITY FRAGMENT FUNCTIONS + +ericw -- GLQuake only uses efrags for static entities, and they're never +removed, so I trimmed out unused functionality and fields in efrag_t. + +Now, efrags are just a linked list for each leaf of the static +entities that touch that leaf. The efrags are hunk-allocated so there is no +fixed limit. + +This is inspired by MH's tutorial, and code from RMQEngine. +http://forums.insideqc.com/viewtopic.php?t=1930 + +=============================================================================== +*/ + +static vec3_t r_emins, r_emaxs; + +static entity_t *r_addent; + + +#define EXTRA_EFRAGS 128 + +// based on RMQEngine +static efrag_t *R_GetEfrag (void) +{ + // we could just Hunk_Alloc a single efrag_t and return it, but since + // the struct is so small (2 pointers) allocate groups of them + // to avoid wasting too much space on the hunk allocation headers. + if (cl.free_efrags) + { + efrag_t *ef = cl.free_efrags; + cl.free_efrags = ef->leafnext; + ef->leafnext = NULL; + + cl.num_efrags++; + + return ef; + } + else + { + int i; + + cl.free_efrags = (efrag_t *) Hunk_AllocName (EXTRA_EFRAGS * sizeof (efrag_t), "efrags"); + + for (i = 0; i < EXTRA_EFRAGS - 1; i++) + cl.free_efrags[i].leafnext = &cl.free_efrags[i + 1]; + + cl.free_efrags[i].leafnext = NULL; + + // call recursively to get a newly allocated free efrag + return R_GetEfrag (); + } +} + +/* +=================== +R_SplitEntityOnNode +=================== +*/ +void R_SplitEntityOnNode (mnode_t *node) +{ + efrag_t *ef; + mplane_t *splitplane; + mleaf_t *leaf; + int sides; + + if (node->contents == CONTENTS_SOLID) + { + return; + } + +// add an efrag if the node is a leaf + + if ( node->contents < 0) + { + if (!r_pefragtopnode) + r_pefragtopnode = node; + + leaf = (mleaf_t *)node; + + // grab an efrag off the free list + ef = R_GetEfrag(); + ef->entity = r_addent; + + // set the leaf links + ef->leafnext = leaf->efrags; + leaf->efrags = ef; + + return; + } + +// NODE_MIXED + + splitplane = node->plane; + sides = BOX_ON_PLANE_SIDE(r_emins, r_emaxs, splitplane); + + if (sides == 3) + { + // split on this plane + // if this is the first splitter of this bmodel, remember it + if (!r_pefragtopnode) + r_pefragtopnode = node; + } + +// recurse down the contacted sides + if (sides & 1) + R_SplitEntityOnNode (node->children[0]); + + if (sides & 2) + R_SplitEntityOnNode (node->children[1]); +} + +/* +=========== +R_CheckEfrags -- johnfitz -- check for excessive efrag count +=========== +*/ +void R_CheckEfrags (void) +{ + if (cls.signon < 2) + return; //don't spam when still parsing signon packet full of static ents + + if (cl.num_efrags > 640 && dev_peakstats.efrags <= 640) + Con_DWarning ("%i efrags exceeds standard limit of 640.\n", cl.num_efrags); + + dev_stats.efrags = cl.num_efrags; + dev_peakstats.efrags = q_max(cl.num_efrags, dev_peakstats.efrags); +} + +/* +=========== +R_AddEfrags +=========== +*/ +void R_AddEfrags (entity_t *ent) +{ + qmodel_t *entmodel; + vec_t scalefactor; + + if (!ent->model) + return; + + r_addent = ent; + + r_pefragtopnode = NULL; + + entmodel = ent->model; + scalefactor = ENTSCALE_DECODE(ent->scale); + if (scalefactor != 1.0f) + { + VectorMA (ent->origin, scalefactor, entmodel->mins, r_emins); + VectorMA (ent->origin, scalefactor, entmodel->maxs, r_emaxs); + } + else + { + VectorAdd (ent->origin, entmodel->mins, r_emins); + VectorAdd (ent->origin, entmodel->maxs, r_emaxs); + } + + R_SplitEntityOnNode (cl.worldmodel->nodes); + + ent->topnode = r_pefragtopnode; + + R_CheckEfrags (); //johnfitz +} + + +/* +================ +R_StoreEfrags -- johnfitz -- pointless switch statement removed. +================ +*/ +void R_StoreEfrags (efrag_t **ppefrag) +{ + entity_t *pent; + efrag_t *pefrag; + + while ((pefrag = *ppefrag) != NULL) + { + pent = pefrag->entity; + + if ((pent->visframe != r_framecount) && (cl_numvisedicts < MAX_VISEDICTS)) + { + cl_visedicts[cl_numvisedicts++] = pent; + pent->visframe = r_framecount; + } + + ppefrag = &pefrag->leafnext; + } +} diff --git a/Quake/gl_rlight.cpp b/Quake/gl_rlight.cpp new file mode 100644 index 0000000..a02a647 --- /dev/null +++ b/Quake/gl_rlight.cpp @@ -0,0 +1,421 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_light.c + +#include "quakedef.hpp" + +static int r_dlightframecount; + +extern cvar_t r_flatlightstyles; //johnfitz + +/* +================== +R_AnimateLight +================== +*/ +void R_AnimateLight (void) +{ + int i,j,k; + +// +// light animations +// 'm' is normal light, 'a' is no light, 'z' is double bright + i = (int)(cl.time*10); + for (j=0 ; jradius * 0.35; + + VectorSubtract (light->origin, r_origin, v); + if (VectorLength (v) < rad) + { // view is inside the dlight + AddLightBlend (1, 0.5, 0, light->radius * 0.0003); + return; + } + + glBegin (GL_TRIANGLE_FAN); + glColor3f (0.2,0.1,0.0); + for (i=0 ; i<3 ; i++) + v[i] = light->origin[i] - vpn[i]*rad; + glVertex3fv (v); + glColor3f (0,0,0); + for (i=16 ; i>=0 ; i--) + { + a = i/16.0 * M_PI*2; + for (j=0 ; j<3 ; j++) + v[j] = light->origin[j] + vright[j]*cos(a)*rad + + vup[j]*sin(a)*rad; + glVertex3fv (v); + } + glEnd (); +} + +/* +============= +R_RenderDlights +============= +*/ +void R_RenderDlights (void) +{ + int i; + dlight_t *l; + + if (!gl_flashblend.value) + return; + + r_dlightframecount = r_framecount + 1; // because the count hasn't advanced yet for this frame + glDepthMask (0); + glDisable (GL_TEXTURE_2D); + glShadeModel (GL_SMOOTH); + glEnable (GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + + l = cl_dlights; + for (i=0 ; idie < cl.time || !l->radius) + continue; + R_RenderDlight (l); + } + + glColor3f (1,1,1); + glDisable (GL_BLEND); + glEnable (GL_TEXTURE_2D); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask (1); +} + + +/* +============================================================================= + +DYNAMIC LIGHTS + +============================================================================= +*/ + +/* +============= +R_MarkLights -- johnfitz -- rewritten to use LordHavoc's lighting speedup +============= +*/ +void R_MarkLights (dlight_t *light, int num, mnode_t *node) +{ + mplane_t *splitplane; + msurface_t *surf; + vec3_t impact; + float dist, l, maxdist; + int i, j, s, t; + +start: + if (node->contents < 0) + return; + + splitplane = node->plane; + if (splitplane->type < 3) + dist = light->origin[splitplane->type] - splitplane->dist; + else + dist = DotProduct (light->origin, splitplane->normal) - splitplane->dist; + + if (dist > light->radius) + { + node = node->children[0]; + goto start; + } + if (dist < -light->radius) + { + node = node->children[1]; + goto start; + } + + maxdist = light->radius*light->radius; +// mark the polygons + surf = cl.worldmodel->surfaces + node->firstsurface; + for (i=0 ; inumsurfaces ; i++, surf++) + { + for (j=0 ; j<3 ; j++) + impact[j] = light->origin[j] - surf->plane->normal[j]*dist; + // clamp center of light to corner and check brightness + l = DotProduct (impact, surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3] - surf->texturemins[0]; + s = l+0.5;if (s < 0) s = 0;else if (s > surf->extents[0]) s = surf->extents[0]; + s = l - s; + l = DotProduct (impact, surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3] - surf->texturemins[1]; + t = l+0.5;if (t < 0) t = 0;else if (t > surf->extents[1]) t = surf->extents[1]; + t = l - t; + // compare to minimum light + if ((s*s+t*t+dist*dist) < maxdist) + { + if (surf->dlightframe != r_dlightframecount) // not dynamic until now + { + surf->dlightbits[num >> 5] = 1U << (num & 31); + surf->dlightframe = r_dlightframecount; + } + else // already dynamic + surf->dlightbits[num >> 5] |= 1U << (num & 31); + } + } + + if (node->children[0]->contents >= 0) + R_MarkLights (light, num, node->children[0]); + if (node->children[1]->contents >= 0) + R_MarkLights (light, num, node->children[1]); +} + +/* +============= +R_PushDlights +============= +*/ +void R_PushDlights (void) +{ + int i; + dlight_t *l; + + if (gl_flashblend.value) + return; + + r_dlightframecount = r_framecount + 1; // because the count hasn't advanced yet for this frame + l = cl_dlights; + + for (i=0 ; idie < cl.time || !l->radius) + continue; + R_MarkLights (l, i, cl.worldmodel->nodes); + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +//mplane_t *lightplane; +vec3_t lightspot; +vec3_t lightcolor; //johnfitz -- lit support via lordhavoc + +/* +============= +RecursiveLightPoint -- johnfitz -- replaced entire function for lit support via lordhavoc +============= +*/ +int RecursiveLightPoint (vec3_t color, mnode_t *node, vec3_t rayorg, vec3_t start, vec3_t end, float *maxdist) +{ + float front, back, frac; + vec3_t mid; + +loc0: + if (node->contents < 0) + return false; // didn't hit anything + +// calculate mid point + if (node->plane->type < 3) + { + front = start[node->plane->type] - node->plane->dist; + back = end[node->plane->type] - node->plane->dist; + } + else + { + front = DotProduct(start, node->plane->normal) - node->plane->dist; + back = DotProduct(end, node->plane->normal) - node->plane->dist; + } + + // LordHavoc: optimized recursion + if ((back < 0) == (front < 0)) +// return RecursiveLightPoint (color, node->children[front < 0], rayorg, start, end, maxdist); + { + node = node->children[front < 0]; + goto loc0; + } + + frac = front / (front-back); + mid[0] = start[0] + (end[0] - start[0])*frac; + mid[1] = start[1] + (end[1] - start[1])*frac; + mid[2] = start[2] + (end[2] - start[2])*frac; + +// go down front side + if (RecursiveLightPoint (color, node->children[front < 0], rayorg, start, mid, maxdist)) + return true; // hit something + else + { + int i, ds, dt; + msurface_t *surf; + // check for impact on this node + VectorCopy (mid, lightspot); + //lightplane = node->plane; + + surf = cl.worldmodel->surfaces + node->firstsurface; + for (i = 0;i < node->numsurfaces;i++, surf++) + { + float sfront, sback, dist; + vec3_t raydelta; + + if (surf->flags & SURF_DRAWTILED) + continue; // no lightmaps + + // ericw -- added double casts to force 64-bit precision. + // Without them the zombie at the start of jam3_ericw.bsp was + // incorrectly being lit up in SSE builds. + ds = (int) ((double) DoublePrecisionDotProduct (mid, surf->texinfo->vecs[0]) + surf->texinfo->vecs[0][3]); + dt = (int) ((double) DoublePrecisionDotProduct (mid, surf->texinfo->vecs[1]) + surf->texinfo->vecs[1][3]); + + if (ds < surf->texturemins[0] || dt < surf->texturemins[1]) + continue; + + ds -= surf->texturemins[0]; + dt -= surf->texturemins[1]; + + if (ds > surf->extents[0] || dt > surf->extents[1]) + continue; + + if (surf->plane->type < 3) + { + sfront = rayorg[surf->plane->type] - surf->plane->dist; + sback = end[surf->plane->type] - surf->plane->dist; + } + else + { + sfront = DotProduct(rayorg, surf->plane->normal) - surf->plane->dist; + sback = DotProduct(end, surf->plane->normal) - surf->plane->dist; + } + VectorSubtract(end, rayorg, raydelta); + dist = sfront / (sfront - sback) * VectorLength(raydelta); + + if (!surf->samples) + { + // We hit a surface that is flagged as lightmapped, but doesn't have actual lightmap info. + // Instead of just returning black, we'll keep looking for nearby surfaces that do have valid samples. + // This fixes occasional pitch-black models in otherwise well-lit areas in DOTM (e.g. mge1m1, mge4m1) + // caused by overlapping surfaces with mixed lighting data. + const float nearby = 8.f; + dist += nearby; + *maxdist = q_min(*maxdist, dist); + continue; + } + + if (dist < *maxdist) + { + // LordHavoc: enhanced to interpolate lighting + byte *lightmap; + int maps, line3, dsfrac = ds & 15, dtfrac = dt & 15, r00 = 0, g00 = 0, b00 = 0, r01 = 0, g01 = 0, b01 = 0, r10 = 0, g10 = 0, b10 = 0, r11 = 0, g11 = 0, b11 = 0; + float scale; + line3 = ((surf->extents[0]>>4)+1)*3; + + lightmap = surf->samples + ((dt>>4) * ((surf->extents[0]>>4)+1) + (ds>>4))*3; // LordHavoc: *3 for color + + for (maps = 0;maps < MAXLIGHTMAPS && surf->styles[maps] != 255;maps++) + { + scale = (float) d_lightstylevalue[surf->styles[maps]] * 1.0 / 256.0; + r00 += (float) lightmap[ 0] * scale;g00 += (float) lightmap[ 1] * scale;b00 += (float) lightmap[2] * scale; + r01 += (float) lightmap[ 3] * scale;g01 += (float) lightmap[ 4] * scale;b01 += (float) lightmap[5] * scale; + r10 += (float) lightmap[line3+0] * scale;g10 += (float) lightmap[line3+1] * scale;b10 += (float) lightmap[line3+2] * scale; + r11 += (float) lightmap[line3+3] * scale;g11 += (float) lightmap[line3+4] * scale;b11 += (float) lightmap[line3+5] * scale; + lightmap += ((surf->extents[0]>>4)+1) * ((surf->extents[1]>>4)+1)*3; // LordHavoc: *3 for colored lighting + } + + color[0] += (float) ((int) ((((((((r11-r10) * dsfrac) >> 4) + r10)-((((r01-r00) * dsfrac) >> 4) + r00)) * dtfrac) >> 4) + ((((r01-r00) * dsfrac) >> 4) + r00))); + color[1] += (float) ((int) ((((((((g11-g10) * dsfrac) >> 4) + g10)-((((g01-g00) * dsfrac) >> 4) + g00)) * dtfrac) >> 4) + ((((g01-g00) * dsfrac) >> 4) + g00))); + color[2] += (float) ((int) ((((((((b11-b10) * dsfrac) >> 4) + b10)-((((b01-b00) * dsfrac) >> 4) + b00)) * dtfrac) >> 4) + ((((b01-b00) * dsfrac) >> 4) + b00))); + } + return true; // success + } + + // go down back side + return RecursiveLightPoint (color, node->children[front >= 0], rayorg, mid, end, maxdist); + } +} + +/* +============= +R_LightPoint -- johnfitz -- replaced entire function for lit support via lordhavoc +============= +*/ +int R_LightPoint (vec3_t p) +{ + vec3_t end; + float maxdist = 8192.f; //johnfitz -- was 2048 + + if (!cl.worldmodel->lightdata) + { + lightcolor[0] = lightcolor[1] = lightcolor[2] = 255; + return 255; + } + + end[0] = p[0]; + end[1] = p[1]; + end[2] = p[2] - maxdist; + + lightcolor[0] = lightcolor[1] = lightcolor[2] = 0; + RecursiveLightPoint (lightcolor, cl.worldmodel->nodes, p, p, end, &maxdist); + return ((lightcolor[0] + lightcolor[1] + lightcolor[2]) * (1.0f / 3.0f)); +} diff --git a/Quake/gl_rmain.cpp b/Quake/gl_rmain.cpp new file mode 100644 index 0000000..3a75fc9 --- /dev/null +++ b/Quake/gl_rmain.cpp @@ -0,0 +1,1133 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_main.c + +#include "quakedef.hpp" + +vec3_t modelorg, r_entorigin; +entity_t *currententity; + +int r_visframecount; // bumped when going to a new PVS +int r_framecount; // used for dlight push checking + +mplane_t frustum[4]; + +//johnfitz -- rendering statistics +int rs_brushpolys, rs_aliaspolys, rs_skypolys; +int rs_dynamiclightmaps, rs_brushpasses, rs_aliaspasses, rs_skypasses; + +// +// view origin +// +vec3_t vup; +vec3_t vpn; +vec3_t vright; +vec3_t r_origin; + +float r_fovx, r_fovy; //johnfitz -- rendering fov may be different becuase of r_waterwarp and r_stereo + +// +// screen size info +// +refdef_t r_refdef; + +mleaf_t *r_viewleaf, *r_oldviewleaf; + +int d_lightstylevalue[256]; // 8.8 fraction of base light value + + +cvar_t r_norefresh = {"r_norefresh","0",CVAR_NONE}; +cvar_t r_drawentities = {"r_drawentities","1",CVAR_NONE}; +cvar_t r_drawviewmodel = {"r_drawviewmodel","1",CVAR_NONE}; +cvar_t r_speeds = {"r_speeds","0",CVAR_NONE}; +cvar_t r_pos = {"r_pos","0",CVAR_NONE}; +cvar_t r_fullbright = {"r_fullbright","0",CVAR_NONE}; +cvar_t r_lightmap = {"r_lightmap","0",CVAR_NONE}; +cvar_t r_shadows = {"r_shadows","0",CVAR_ARCHIVE}; +cvar_t r_wateralpha = {"r_wateralpha","1",CVAR_ARCHIVE}; +cvar_t r_litwater = {"r_litwater","1",CVAR_NONE}; +cvar_t r_dynamic = {"r_dynamic","1",CVAR_ARCHIVE}; +cvar_t r_novis = {"r_novis","0",CVAR_ARCHIVE}; + +cvar_t gl_finish = {"gl_finish","0",CVAR_NONE}; +cvar_t gl_clear = {"gl_clear","1",CVAR_NONE}; +cvar_t gl_cull = {"gl_cull","1",CVAR_NONE}; +cvar_t gl_smoothmodels = {"gl_smoothmodels","1",CVAR_NONE}; +cvar_t gl_affinemodels = {"gl_affinemodels","0",CVAR_NONE}; +cvar_t gl_polyblend = {"gl_polyblend","1",CVAR_NONE}; +cvar_t gl_flashblend = {"gl_flashblend","0",CVAR_ARCHIVE}; +cvar_t gl_playermip = {"gl_playermip","0",CVAR_NONE}; +cvar_t gl_nocolors = {"gl_nocolors","0",CVAR_NONE}; + +//johnfitz -- new cvars +cvar_t r_stereo = {"r_stereo","0",CVAR_NONE}; +cvar_t r_stereodepth = {"r_stereodepth","128",CVAR_NONE}; +cvar_t r_clearcolor = {"r_clearcolor","2",CVAR_ARCHIVE}; +cvar_t r_drawflat = {"r_drawflat","0",CVAR_NONE}; +cvar_t r_flatlightstyles = {"r_flatlightstyles", "0", CVAR_NONE}; +cvar_t gl_fullbrights = {"gl_fullbrights", "1", CVAR_ARCHIVE}; +cvar_t gl_farclip = {"gl_farclip", "65536", CVAR_ARCHIVE}; +cvar_t gl_overbright = {"gl_overbright", "1", CVAR_ARCHIVE}; +cvar_t gl_overbright_models = {"gl_overbright_models", "1", CVAR_ARCHIVE}; +cvar_t r_oldskyleaf = {"r_oldskyleaf", "0", CVAR_NONE}; +cvar_t r_drawworld = {"r_drawworld", "1", CVAR_NONE}; +cvar_t r_showtris = {"r_showtris", "0", CVAR_NONE}; +cvar_t r_showbboxes = {"r_showbboxes", "0", CVAR_NONE}; +cvar_t r_lerpmodels = {"r_lerpmodels", "1", CVAR_NONE}; +cvar_t r_lerpmove = {"r_lerpmove", "1", CVAR_NONE}; +cvar_t r_nolerp_list = {"r_nolerp_list", "progs/flame.mdl,progs/flame2.mdl,progs/braztall.mdl,progs/brazshrt.mdl,progs/longtrch.mdl,progs/flame_pyre.mdl,progs/v_saw.mdl,progs/v_xfist.mdl,progs/h2stuff/newfire.mdl", CVAR_NONE}; +cvar_t r_noshadow_list = {"r_noshadow_list", "progs/flame2.mdl,progs/flame.mdl,progs/bolt1.mdl,progs/bolt2.mdl,progs/bolt3.mdl,progs/laser.mdl", CVAR_NONE}; + +extern cvar_t r_vfog; +//johnfitz + +cvar_t gl_zfix = {"gl_zfix", "0", CVAR_NONE}; // QuakeSpasm z-fighting fix + +cvar_t r_lavaalpha = {"r_lavaalpha","0",CVAR_NONE}; +cvar_t r_telealpha = {"r_telealpha","0",CVAR_NONE}; +cvar_t r_slimealpha = {"r_slimealpha","0",CVAR_NONE}; + +float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; + +qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz + +cvar_t r_scale = {"r_scale", "1", CVAR_ARCHIVE}; + +//============================================================================== +// +// GLSL GAMMA CORRECTION +// +//============================================================================== + +static GLuint r_gamma_texture; +static GLuint r_gamma_program; +static int r_gamma_texture_width, r_gamma_texture_height; + +// uniforms used in gamma shader +static GLint gammaLoc; +static GLint contrastLoc; +static GLint textureLoc; + +/* +============= +GLSLGamma_DeleteTexture +============= +*/ +void GLSLGamma_DeleteTexture (void) +{ + glDeleteTextures (1, &r_gamma_texture); + r_gamma_texture = 0; + r_gamma_program = 0; // deleted in R_DeleteShaders +} + +/* +============= +GLSLGamma_CreateShaders +============= +*/ +static void GLSLGamma_CreateShaders (void) +{ + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "void main(void) {\n" + " gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0);\n" + " gl_TexCoord[0] = gl_MultiTexCoord0;\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D GammaTexture;\n" + "uniform float GammaValue;\n" + "uniform float ContrastValue;\n" + "\n" + "void main(void) {\n" + " vec4 frag = texture2D(GammaTexture, gl_TexCoord[0].xy);\n" + " frag.rgb = frag.rgb * ContrastValue;\n" + " gl_FragColor = vec4(pow(frag.rgb, vec3(GammaValue)), 1.0);\n" + "}\n"; + + if (!gl_glsl_gamma_able) + return; + + r_gamma_program = GL_CreateProgram (vertSource, fragSource, 0, NULL); + +// get uniform locations + gammaLoc = GL_GetUniformLocation (&r_gamma_program, "GammaValue"); + contrastLoc = GL_GetUniformLocation (&r_gamma_program, "ContrastValue"); + textureLoc = GL_GetUniformLocation (&r_gamma_program, "GammaTexture"); +} + +/* +============= +GLSLGamma_GammaCorrect +============= +*/ +void GLSLGamma_GammaCorrect (void) +{ + float smax, tmax; + + if (!gl_glsl_gamma_able) + return; + + if (vid_gamma.value == 1 && vid_contrast.value == 1) + return; + +// create render-to-texture texture if needed + if (!r_gamma_texture) + { + glGenTextures (1, &r_gamma_texture); + glBindTexture (GL_TEXTURE_2D, r_gamma_texture); + + r_gamma_texture_width = glwidth; + r_gamma_texture_height = glheight; + + if (!gl_texture_NPOT) + { + r_gamma_texture_width = TexMgr_Pad(r_gamma_texture_width); + r_gamma_texture_height = TexMgr_Pad(r_gamma_texture_height); + } + + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, r_gamma_texture_width, r_gamma_texture_height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + +// create shader if needed + if (!r_gamma_program) + { + GLSLGamma_CreateShaders (); + if (!r_gamma_program) + { + Sys_Error("GLSLGamma_CreateShaders failed"); + } + } + +// copy the framebuffer to the texture + GL_DisableMultitexture(); + glBindTexture (GL_TEXTURE_2D, r_gamma_texture); + glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, glx, gly, glwidth, glheight); + +// draw the texture back to the framebuffer with a fragment shader + GL_UseProgramFunc (r_gamma_program); + GL_Uniform1fFunc (gammaLoc, vid_gamma.value); + GL_Uniform1fFunc (contrastLoc, q_min(2.0f, q_max(1.0f, vid_contrast.value))); + GL_Uniform1iFunc (textureLoc, 0); // use texture unit 0 + + glDisable (GL_ALPHA_TEST); + glDisable (GL_DEPTH_TEST); + + glViewport (glx, gly, glwidth, glheight); + + smax = glwidth/(float)r_gamma_texture_width; + tmax = glheight/(float)r_gamma_texture_height; + + glBegin (GL_QUADS); + glTexCoord2f (0, 0); + glVertex2f (-1, -1); + glTexCoord2f (smax, 0); + glVertex2f (1, -1); + glTexCoord2f (smax, tmax); + glVertex2f (1, 1); + glTexCoord2f (0, tmax); + glVertex2f (-1, 1); + glEnd (); + + GL_UseProgramFunc (0); + +// clear cached binding + GL_ClearBindings (); +} + +/* +================= +R_CullBox -- johnfitz -- replaced with new function from lordhavoc + +Returns true if the box is completely outside the frustum +================= +*/ +qboolean R_CullBox (vec3_t emins, vec3_t emaxs) +{ + int i; + mplane_t *p; + byte signbits; + float vec[3]; + + for (i = 0;i < 4;i++) + { + p = frustum + i; + signbits = p->signbits; + vec[0] = ((signbits & 1) ? emins : emaxs)[0]; + vec[1] = ((signbits & 2) ? emins : emaxs)[1]; + vec[2] = ((signbits & 4) ? emins : emaxs)[2]; + if (p->normal[0]*vec[0] + p->normal[1]*vec[1] + p->normal[2]*vec[2] < p->dist) + return true; + } + return false; +} + +/* +=============== +R_CullModelForEntity -- johnfitz -- uses correct bounds based on rotation +=============== +*/ +qboolean R_CullModelForEntity (entity_t *e) +{ + vec3_t mins, maxs; + vec_t scalefactor, *minbounds, *maxbounds; + + if (e->angles[0] || e->angles[2]) //pitch or roll + { + minbounds = e->model->rmins; + maxbounds = e->model->rmaxs; + } + else if (e->angles[1]) //yaw + { + minbounds = e->model->ymins; + maxbounds = e->model->ymaxs; + } + else //no rotation + { + minbounds = e->model->mins; + maxbounds = e->model->maxs; + } + + scalefactor = ENTSCALE_DECODE(e->scale); + if (scalefactor != 1.0f) + { + VectorMA (e->origin, scalefactor, minbounds, mins); + VectorMA (e->origin, scalefactor, maxbounds, maxs); + } + else + { + VectorAdd (e->origin, minbounds, mins); + VectorAdd (e->origin, maxbounds, maxs); + } + + return R_CullBox (mins, maxs); +} + +/* +=============== +R_RotateForEntity -- johnfitz -- modified to take origin and angles instead of pointer to entity +=============== +*/ +void R_RotateForEntity (vec3_t origin, vec3_t angles, unsigned char scale) +{ + float scalefactor = ENTSCALE_DECODE(scale); + glTranslatef (origin[0], origin[1], origin[2]); + glRotatef (angles[1], 0, 0, 1); + glRotatef (-angles[0], 0, 1, 0); + glRotatef (angles[2], 1, 0, 0); + if (scalefactor != 1.0f) + glScalef(scalefactor, scalefactor, scalefactor); +} + +/* +============= +GL_PolygonOffset -- johnfitz + +negative offset moves polygon closer to camera +============= +*/ +void GL_PolygonOffset (int offset) +{ + if (offset > 0) + { + glEnable (GL_POLYGON_OFFSET_FILL); + glEnable (GL_POLYGON_OFFSET_LINE); + glPolygonOffset(1, offset); + } + else if (offset < 0) + { + glEnable (GL_POLYGON_OFFSET_FILL); + glEnable (GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-1, offset); + } + else + { + glDisable (GL_POLYGON_OFFSET_FILL); + glDisable (GL_POLYGON_OFFSET_LINE); + } +} + +//============================================================================== +// +// SETUP FRAME +// +//============================================================================== + +int SignbitsForPlane (mplane_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<contents); + V_CalcBlend (); + + //johnfitz -- calculate r_fovx and r_fovy here + r_fovx = r_refdef.fov_x; + r_fovy = r_refdef.fov_y; + if (r_waterwarp.value) + { + int contents = Mod_PointInLeaf (r_origin, cl.worldmodel)->contents; + if (contents == CONTENTS_WATER || contents == CONTENTS_SLIME || contents == CONTENTS_LAVA) + { + //variance is a percentage of width, where width = 2 * tan(fov / 2) otherwise the effect is too dramatic at high FOV and too subtle at low FOV. what a mess! + r_fovx = atan(tan(DEG2RAD(r_refdef.fov_x) / 2) * (0.97 + sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180; + r_fovy = atan(tan(DEG2RAD(r_refdef.fov_y) / 2) * (1.03 - sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180; + } + } + //johnfitz + + R_SetFrustum (r_fovx, r_fovy); //johnfitz -- use r_fov* vars + + R_MarkSurfaces (); //johnfitz -- create texture chains from PVS + + R_UpdateWarpTextures (); //johnfitz -- do this before R_Clear + + R_Clear (); + + //johnfitz -- cheat-protect some draw modes + r_drawflat_cheatsafe = r_fullbright_cheatsafe = r_lightmap_cheatsafe = false; + r_drawworld_cheatsafe = true; + if (cl.maxclients == 1) + { + if (!r_drawworld.value) r_drawworld_cheatsafe = false; + + if (r_drawflat.value) r_drawflat_cheatsafe = true; + else if (r_fullbright.value || !cl.worldmodel->lightdata) r_fullbright_cheatsafe = true; + else if (r_lightmap.value) r_lightmap_cheatsafe = true; + } + //johnfitz +} + +//============================================================================== +// +// RENDER VIEW +// +//============================================================================== + +/* +============= +R_DrawEntitiesOnList +============= +*/ +void R_DrawEntitiesOnList (qboolean alphapass) //johnfitz -- added parameter +{ + int i; + + if (!r_drawentities.value) + return; + + //johnfitz -- sprites are not a special case + for (i=0 ; ialpha) < 1 && !alphapass) || + (ENTALPHA_DECODE(currententity->alpha) == 1 && alphapass)) + continue; + + //johnfitz -- chasecam + if (currententity == &cl_entities[cl.viewentity]) + currententity->angles[0] *= 0.3; + //johnfitz + + switch (currententity->model->type) + { + case mod_alias: + R_DrawAliasModel (currententity); + break; + case mod_brush: + R_DrawBrushModel (currententity); + break; + case mod_sprite: + R_DrawSpriteModel (currententity); + break; + } + } +} + +/* +============= +R_DrawViewModel -- johnfitz -- gutted +============= +*/ +void R_DrawViewModel (void) +{ + if (!r_drawviewmodel.value || !r_drawentities.value || chase_active.value) + return; + + if (cl.items & IT_INVISIBILITY || cl.stats[STAT_HEALTH] <= 0) + return; + + currententity = &cl.viewent; + if (!currententity->model) + return; + + //johnfitz -- this fixes a crash + if (currententity->model->type != mod_alias) + return; + //johnfitz + + // hack the depth range to prevent view model from poking into walls + glDepthRange (0, 0.3); + R_DrawAliasModel (currententity); + glDepthRange (0, 1); +} + +/* +================ +R_EmitWirePoint -- johnfitz -- draws a wireframe cross shape for point entities +================ +*/ +void R_EmitWirePoint (vec3_t origin) +{ + const int size = 8; + + glBegin (GL_LINES); + glVertex3f (origin[0]-size, origin[1], origin[2]); + glVertex3f (origin[0]+size, origin[1], origin[2]); + glVertex3f (origin[0], origin[1]-size, origin[2]); + glVertex3f (origin[0], origin[1]+size, origin[2]); + glVertex3f (origin[0], origin[1], origin[2]-size); + glVertex3f (origin[0], origin[1], origin[2]+size); + glEnd (); +} + +/* +================ +R_EmitWireBox -- johnfitz -- draws one axis aligned bounding box +================ +*/ +void R_EmitWireBox (vec3_t mins, vec3_t maxs) +{ + glBegin (GL_QUAD_STRIP); + glVertex3f (mins[0], mins[1], mins[2]); + glVertex3f (mins[0], mins[1], maxs[2]); + glVertex3f (maxs[0], mins[1], mins[2]); + glVertex3f (maxs[0], mins[1], maxs[2]); + glVertex3f (maxs[0], maxs[1], mins[2]); + glVertex3f (maxs[0], maxs[1], maxs[2]); + glVertex3f (mins[0], maxs[1], mins[2]); + glVertex3f (mins[0], maxs[1], maxs[2]); + glVertex3f (mins[0], mins[1], mins[2]); + glVertex3f (mins[0], mins[1], maxs[2]); + glEnd (); +} + +/* +================ +R_ShowBoundingBoxes -- johnfitz + +draw bounding boxes -- the server-side boxes, not the renderer cullboxes +================ +*/ +void R_ShowBoundingBoxes (void) +{ + extern edict_t *sv_player; + vec3_t mins,maxs; + edict_t *ed; + int i; + + if (!r_showbboxes.value || cl.maxclients > 1 || !r_drawentities.value || !sv.active) + return; + + glDisable (GL_DEPTH_TEST); + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + GL_PolygonOffset (OFFSET_SHOWTRIS); + glDisable (GL_TEXTURE_2D); + glDisable (GL_CULL_FACE); + glColor3f (1,1,1); + + for (i=1, ed=NEXT_EDICT(sv.edicts) ; ifree) + continue; //don't draw player's own bbox or freed edicts + +// if (r_showbboxes.value != 2) +// if (!SV_VisibleToClient (sv_player, ed, sv.worldmodel)) +// continue; //don't draw if not in pvs + + if (ed->v.mins[0] == ed->v.maxs[0] && ed->v.mins[1] == ed->v.maxs[1] && ed->v.mins[2] == ed->v.maxs[2]) + { + //point entity + R_EmitWirePoint (ed->v.origin); + } + else + { + //box entity + VectorAdd (ed->v.mins, ed->v.origin, mins); + VectorAdd (ed->v.maxs, ed->v.origin, maxs); + R_EmitWireBox (mins, maxs); + } + } + + glColor3f (1,1,1); + glEnable (GL_TEXTURE_2D); + glEnable (GL_CULL_FACE); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + GL_PolygonOffset (OFFSET_NONE); + glEnable (GL_DEPTH_TEST); + + Sbar_Changed (); //so we don't get dots collecting on the statusbar +} + +/* +================ +R_ShowTris -- johnfitz +================ +*/ +void R_ShowTris (void) +{ + extern cvar_t r_particles; + int i; + + if (r_showtris.value < 1 || r_showtris.value > 2 || cl.maxclients > 1) + return; + + if (r_showtris.value == 1) + glDisable (GL_DEPTH_TEST); + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + GL_PolygonOffset (OFFSET_SHOWTRIS); + glDisable (GL_TEXTURE_2D); + glColor3f (1,1,1); +// glEnable (GL_BLEND); +// glBlendFunc (GL_ONE, GL_ONE); + + if (r_drawworld.value) + { + R_DrawWorld_ShowTris (); + } + + if (r_drawentities.value) + { + for (i=0 ; iangles[0] *= 0.3; + + switch (currententity->model->type) + { + case mod_brush: + R_DrawBrushModel_ShowTris (currententity); + break; + case mod_alias: + R_DrawAliasModel_ShowTris (currententity); + break; + case mod_sprite: + R_DrawSpriteModel (currententity); + break; + default: + break; + } + } + + // viewmodel + currententity = &cl.viewent; + if (r_drawviewmodel.value + && !chase_active.value + && cl.stats[STAT_HEALTH] > 0 + && !(cl.items & IT_INVISIBILITY) + && currententity->model + && currententity->model->type == mod_alias) + { + glDepthRange (0, 0.3); + R_DrawAliasModel_ShowTris (currententity); + glDepthRange (0, 1); + } + } + + if (r_particles.value) + { + R_DrawParticles_ShowTris (); + } + +// glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +// glDisable (GL_BLEND); + glColor3f (1,1,1); + glEnable (GL_TEXTURE_2D); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + GL_PolygonOffset (OFFSET_NONE); + if (r_showtris.value == 1) + glEnable (GL_DEPTH_TEST); + + Sbar_Changed (); //so we don't get dots collecting on the statusbar +} + +/* +================ +R_DrawShadows +================ +*/ +void R_DrawShadows (void) +{ + int i; + + if (!r_shadows.value || !r_drawentities.value || r_drawflat_cheatsafe || r_lightmap_cheatsafe) + return; + + // Use stencil buffer to prevent self-intersecting shadows, from Baker (MarkV) + if (gl_stencilbits) + { + glClear(GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_EQUAL, 0, ~0); + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + glEnable(GL_STENCIL_TEST); + } + + for (i=0 ; imodel->type != mod_alias) + continue; + + if (currententity == &cl.viewent) + return; + + GL_DrawAliasShadow (currententity); + } + + if (gl_stencilbits) + { + glDisable(GL_STENCIL_TEST); + } +} + +/* +================ +R_RenderScene +================ +*/ +void R_RenderScene (void) +{ + R_SetupScene (); //johnfitz -- this does everything that should be done once per call to RenderScene + + Fog_EnableGFog (); //johnfitz + + Sky_DrawSky (); //johnfitz + + R_DrawWorld (); + + S_ExtraUpdate (); // don't let sound get messed up if going slow + + R_DrawShadows (); //johnfitz -- render entity shadows + + R_DrawEntitiesOnList (false); //johnfitz -- false means this is the pass for nonalpha entities + + R_DrawWorld_Water (); //johnfitz -- drawn here since they might have transparency + + R_DrawEntitiesOnList (true); //johnfitz -- true means this is the pass for alpha entities + + R_RenderDlights (); //triangle fan dlights -- johnfitz -- moved after water + + R_DrawParticles (); + + Fog_DisableGFog (); //johnfitz + + R_DrawViewModel (); //johnfitz -- moved here from R_RenderView + + R_ShowTris (); //johnfitz + + R_ShowBoundingBoxes (); //johnfitz +} + +static GLuint r_scaleview_texture; +static int r_scaleview_texture_width, r_scaleview_texture_height; + +/* +============= +R_ScaleView_DeleteTexture +============= +*/ +void R_ScaleView_DeleteTexture (void) +{ + glDeleteTextures (1, &r_scaleview_texture); + r_scaleview_texture = 0; +} + +/* +================ +R_ScaleView + +The r_scale cvar allows rendering the 3D view at 1/2, 1/3, or 1/4 resolution. +This function scales the reduced resolution 3D view back up to fill +r_refdef.vrect. This is for emulating a low-resolution pixellated look, +or possibly as a perforance boost on slow graphics cards. +================ +*/ +void R_ScaleView (void) +{ + float smax, tmax; + int scale; + int srcx, srcy, srcw, srch; + + // copied from R_SetupGL() + scale = CLAMP(1, (int)r_scale.value, 4); + srcx = glx + r_refdef.vrect.x; + srcy = gly + glheight - r_refdef.vrect.y - r_refdef.vrect.height; + srcw = r_refdef.vrect.width / scale; + srch = r_refdef.vrect.height / scale; + + if (scale == 1) + return; + + // make sure texture unit 0 is selected + GL_DisableMultitexture (); + + // create (if needed) and bind the render-to-texture texture + if (!r_scaleview_texture) + { + glGenTextures (1, &r_scaleview_texture); + + r_scaleview_texture_width = 0; + r_scaleview_texture_height = 0; + } + glBindTexture (GL_TEXTURE_2D, r_scaleview_texture); + + // resize render-to-texture texture if needed + if (r_scaleview_texture_width < srcw + || r_scaleview_texture_height < srch) + { + r_scaleview_texture_width = srcw; + r_scaleview_texture_height = srch; + + if (!gl_texture_NPOT) + { + r_scaleview_texture_width = TexMgr_Pad(r_scaleview_texture_width); + r_scaleview_texture_height = TexMgr_Pad(r_scaleview_texture_height); + } + + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, r_scaleview_texture_width, r_scaleview_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + // copy the framebuffer to the texture + glBindTexture (GL_TEXTURE_2D, r_scaleview_texture); + glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, srcx, srcy, srcw, srch); + + // draw the texture back to the framebuffer + glDisable (GL_ALPHA_TEST); + glDisable (GL_DEPTH_TEST); + glDisable (GL_CULL_FACE); + glDisable (GL_BLEND); + + glViewport (srcx, srcy, r_refdef.vrect.width, r_refdef.vrect.height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity (); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity (); + + // correction factor if we lack NPOT textures, normally these are 1.0f + smax = srcw/(float)r_scaleview_texture_width; + tmax = srch/(float)r_scaleview_texture_height; + + glBegin (GL_QUADS); + glTexCoord2f (0, 0); + glVertex2f (-1, -1); + glTexCoord2f (smax, 0); + glVertex2f (1, -1); + glTexCoord2f (smax, tmax); + glVertex2f (1, 1); + glTexCoord2f (0, tmax); + glVertex2f (-1, 1); + glEnd (); + + // clear cached binding + GL_ClearBindings (); +} + +/* +================ +R_RenderView +================ +*/ +void R_RenderView (void) +{ + double time1, time2; + + if (r_norefresh.value) + return; + + if (!cl.worldmodel) + Sys_Error ("R_RenderView: NULL worldmodel"); + + time1 = 0; /* avoid compiler warning */ + if (r_speeds.value) + { + glFinish (); + time1 = Sys_DoubleTime (); + + //johnfitz -- rendering statistics + rs_brushpolys = rs_aliaspolys = rs_skypolys = + rs_dynamiclightmaps = rs_aliaspasses = rs_skypasses = rs_brushpasses = 0; + } + else if (gl_finish.value) + glFinish (); + + R_SetupView (); //johnfitz -- this does everything that should be done once per frame + + //johnfitz -- stereo rendering -- full of hacky goodness + if (r_stereo.value) + { + float eyesep = CLAMP(-8.0f, r_stereo.value, 8.0f); + float fdepth = CLAMP(32.0f, r_stereodepth.value, 1024.0f); + + AngleVectors (r_refdef.viewangles, vpn, vright, vup); + + //render left eye (red) + glColorMask(1, 0, 0, 1); + VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg); + frustum_skew = 0.5 * eyesep * NEARCLIP / fdepth; + srand((int) (cl.time * 1000)); //sync random stuff between eyes + + R_RenderScene (); + + //render right eye (cyan) + glClear (GL_DEPTH_BUFFER_BIT); + glColorMask(0, 1, 1, 1); + VectorMA (r_refdef.vieworg, 1.0f * eyesep, vright, r_refdef.vieworg); + frustum_skew = -frustum_skew; + srand((int) (cl.time * 1000)); //sync random stuff between eyes + + R_RenderScene (); + + //restore + glColorMask(1, 1, 1, 1); + VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg); + frustum_skew = 0.0f; + } + else + { + R_RenderScene (); + } + //johnfitz + + R_ScaleView (); + + //johnfitz -- modified r_speeds output + time2 = Sys_DoubleTime (); + if (r_pos.value) + Con_Printf ("x %i y %i z %i (pitch %i yaw %i roll %i)\n", + (int)cl_entities[cl.viewentity].origin[0], + (int)cl_entities[cl.viewentity].origin[1], + (int)cl_entities[cl.viewentity].origin[2], + (int)cl.viewangles[PITCH], + (int)cl.viewangles[YAW], + (int)cl.viewangles[ROLL]); + else if (r_speeds.value == 2) + Con_Printf ("%3i ms %4i/%4i wpoly %4i/%4i epoly %3i lmap %4i/%4i sky %1.1f mtex\n", + (int)((time2-time1)*1000), + rs_brushpolys, + rs_brushpasses, + rs_aliaspolys, + rs_aliaspasses, + rs_dynamiclightmaps, + rs_skypolys, + rs_skypasses, + TexMgr_FrameUsage ()); + else if (r_speeds.value) + Con_Printf ("%3i ms %4i wpoly %4i epoly %3i lmap\n", + (int)((time2-time1)*1000), + rs_brushpolys, + rs_aliaspolys, + rs_dynamiclightmaps); + //johnfitz +} + diff --git a/Quake/gl_rmisc.cpp b/Quake/gl_rmisc.cpp new file mode 100644 index 0000000..81a0900 --- /dev/null +++ b/Quake/gl_rmisc.cpp @@ -0,0 +1,645 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_misc.c + +#include "quakedef.hpp" + +//johnfitz -- new cvars +extern cvar_t r_stereo; +extern cvar_t r_stereodepth; +extern cvar_t r_clearcolor; +extern cvar_t r_drawflat; +extern cvar_t r_flatlightstyles; +extern cvar_t gl_fullbrights; +extern cvar_t gl_farclip; +extern cvar_t gl_overbright; +extern cvar_t gl_overbright_models; +extern cvar_t r_waterquality; +extern cvar_t r_oldwater; +extern cvar_t r_waterwarp; +extern cvar_t r_oldskyleaf; +extern cvar_t r_drawworld; +extern cvar_t r_showtris; +extern cvar_t r_showbboxes; +extern cvar_t r_lerpmodels; +extern cvar_t r_lerpmove; +extern cvar_t r_nolerp_list; +extern cvar_t r_noshadow_list; +//johnfitz +extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix + +extern gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz + +cvar_t r_lightmapwide = {"r_lightmapwide","0",CVAR_ROM}; + + +/* +==================== +GL_Overbright_f -- johnfitz +==================== +*/ +static void GL_Overbright_f (cvar_t *var) +{ + R_RebuildAllLightmaps (); +} + +/* +==================== +GL_Fullbrights_f -- johnfitz +==================== +*/ +static void GL_Fullbrights_f (cvar_t *var) +{ + TexMgr_ReloadNobrightImages (); +} + +/* +==================== +R_SetClearColor_f -- johnfitz +==================== +*/ +static void R_SetClearColor_f (cvar_t *var) +{ + byte *rgb; + int s; + + s = (int)r_clearcolor.value & 0xFF; + rgb = (byte*)(d_8to24table + s); + glClearColor (rgb[0]/255.0,rgb[1]/255.0,rgb[2]/255.0,0); +} + +/* +=============== +R_Model_ExtraFlags_List_f -- johnfitz -- called when r_nolerp_list or r_noshadow_list cvar changes +=============== +*/ +static void R_Model_ExtraFlags_List_f (cvar_t *var) +{ + int i; + for (i=0; i < MAX_MODELS; i++) + Mod_SetExtraFlags (cl.model_precache[i]); +} + +/* +==================== +R_SetWateralpha_f -- ericw +==================== +*/ +static void R_SetWateralpha_f (cvar_t *var) +{ + map_wateralpha = var->value; +} + +/* +==================== +R_SetLavaalpha_f -- ericw +==================== +*/ +static void R_SetLavaalpha_f (cvar_t *var) +{ + map_lavaalpha = var->value; +} + +/* +==================== +R_SetTelealpha_f -- ericw +==================== +*/ +static void R_SetTelealpha_f (cvar_t *var) +{ + map_telealpha = var->value; +} + +/* +==================== +R_SetSlimealpha_f -- ericw +==================== +*/ +static void R_SetSlimealpha_f (cvar_t *var) +{ + map_slimealpha = var->value; +} + +/* +==================== +GL_WaterAlphaForSurfface -- ericw +==================== +*/ +float GL_WaterAlphaForSurface (msurface_t *fa) +{ + if (fa->flags & SURF_DRAWLAVA) + return map_lavaalpha > 0 ? map_lavaalpha : map_wateralpha; + else if (fa->flags & SURF_DRAWTELE) + return map_telealpha > 0 ? map_telealpha : map_wateralpha; + else if (fa->flags & SURF_DRAWSLIME) + return map_slimealpha > 0 ? map_slimealpha : map_wateralpha; + else + return map_wateralpha; +} + + +/* +=============== +R_Init +=============== +*/ +void R_Init (void) +{ + Cmd_AddCommand ("timerefresh", R_TimeRefresh_f); + Cmd_AddCommand ("pointfile", R_ReadPointFile_f); + + Cvar_RegisterVariable (&r_norefresh); + Cvar_RegisterVariable (&r_lightmap); + Cvar_RegisterVariable (&r_fullbright); + Cvar_RegisterVariable (&r_drawentities); + Cvar_RegisterVariable (&r_drawviewmodel); + Cvar_RegisterVariable (&r_shadows); + Cvar_RegisterVariable (&r_wateralpha); + Cvar_SetCallback (&r_wateralpha, R_SetWateralpha_f); + Cvar_RegisterVariable (&r_litwater); + Cvar_RegisterVariable (&r_dynamic); + Cvar_RegisterVariable (&r_novis); + Cvar_RegisterVariable (&r_speeds); + Cvar_RegisterVariable (&r_pos); + + Cvar_RegisterVariable (&gl_finish); + Cvar_RegisterVariable (&gl_clear); + Cvar_RegisterVariable (&gl_cull); + Cvar_RegisterVariable (&gl_smoothmodels); + Cvar_RegisterVariable (&gl_affinemodels); + Cvar_RegisterVariable (&gl_polyblend); + Cvar_RegisterVariable (&gl_flashblend); + Cvar_RegisterVariable (&gl_playermip); + Cvar_RegisterVariable (&gl_nocolors); + + //johnfitz -- new cvars + Cvar_RegisterVariable (&r_stereo); + Cvar_RegisterVariable (&r_stereodepth); + Cvar_RegisterVariable (&r_clearcolor); + Cvar_SetCallback (&r_clearcolor, R_SetClearColor_f); + Cvar_RegisterVariable (&r_waterquality); + Cvar_RegisterVariable (&r_oldwater); + Cvar_RegisterVariable (&r_waterwarp); + Cvar_RegisterVariable (&r_drawflat); + Cvar_RegisterVariable (&r_flatlightstyles); + Cvar_RegisterVariable (&r_oldskyleaf); + Cvar_RegisterVariable (&r_drawworld); + Cvar_RegisterVariable (&r_showtris); + Cvar_RegisterVariable (&r_showbboxes); + Cvar_RegisterVariable (&gl_farclip); + Cvar_RegisterVariable (&gl_fullbrights); + Cvar_RegisterVariable (&gl_overbright); + Cvar_SetCallback (&gl_fullbrights, GL_Fullbrights_f); + Cvar_SetCallback (&gl_overbright, GL_Overbright_f); + Cvar_RegisterVariable (&gl_overbright_models); + Cvar_RegisterVariable (&r_lerpmodels); + Cvar_RegisterVariable (&r_lerpmove); + Cvar_RegisterVariable (&r_nolerp_list); + Cvar_SetCallback (&r_nolerp_list, R_Model_ExtraFlags_List_f); + Cvar_RegisterVariable (&r_noshadow_list); + Cvar_SetCallback (&r_noshadow_list, R_Model_ExtraFlags_List_f); + //johnfitz + Cvar_RegisterVariable (&r_lightmapwide); + Cvar_SetROM ("r_lightmapwide", gl_packed_pixels ? "1" : "0"); + + Cvar_RegisterVariable (&gl_zfix); // QuakeSpasm z-fighting fix + Cvar_RegisterVariable (&r_lavaalpha); + Cvar_RegisterVariable (&r_telealpha); + Cvar_RegisterVariable (&r_slimealpha); + Cvar_RegisterVariable (&r_scale); + Cvar_SetCallback (&r_lavaalpha, R_SetLavaalpha_f); + Cvar_SetCallback (&r_telealpha, R_SetTelealpha_f); + Cvar_SetCallback (&r_slimealpha, R_SetSlimealpha_f); + + R_InitParticles (); + R_SetClearColor_f (&r_clearcolor); //johnfitz + + Sky_Init (); //johnfitz + Fog_Init (); //johnfitz +} + +/* +=============== +R_TranslatePlayerSkin -- johnfitz -- rewritten. also, only handles new colors, not new skins +=============== +*/ +void R_TranslatePlayerSkin (int playernum) +{ + int top, bottom; + + top = (cl.scores[playernum].colors & 0xf0)>>4; + bottom = cl.scores[playernum].colors &15; + + //FIXME: if gl_nocolors is on, then turned off, the textures may be out of sync with the scoreboard colors. + if (!gl_nocolors.value) + { + if (playertextures[playernum]) + TexMgr_ReloadImage (playertextures[playernum], top, bottom); + } +} + +/* +=============== +R_TranslateNewPlayerSkin -- johnfitz -- split off of TranslatePlayerSkin -- this is called when +the skin or model actually changes, instead of just new colors +added bug fix from bengt jardup +=============== +*/ +void R_TranslateNewPlayerSkin (int playernum) +{ + char name[64]; + byte *pixels; + aliashdr_t *paliashdr; + int skinnum; + +//get correct texture pixels + currententity = &cl_entities[1+playernum]; + + if (!currententity->model || currententity->model->type != mod_alias) + return; + + paliashdr = (aliashdr_t *)Mod_Extradata (currententity->model); + + skinnum = currententity->skinnum; + + //TODO: move these tests to the place where skinnum gets received from the server + if (skinnum < 0 || skinnum >= paliashdr->numskins) + { + Con_DPrintf("(%d): Invalid player skin #%d\n", playernum, skinnum); + skinnum = 0; + } + + pixels = (byte *)paliashdr + paliashdr->texels[skinnum]; // This is not a persistent place! + +//upload new image + q_snprintf(name, sizeof(name), "player_%i", playernum); + playertextures[playernum] = TexMgr_LoadImage (currententity->model, name, paliashdr->skinwidth, paliashdr->skinheight, + SRC_INDEXED, pixels, paliashdr->gltextures[skinnum][0]->source_file, paliashdr->gltextures[skinnum][0]->source_offset, TEXPREF_PAD | TEXPREF_OVERWRITE); + +//now recolor it + R_TranslatePlayerSkin (playernum); +} + +/* +=============== +R_NewGame -- johnfitz -- handle a game switch +=============== +*/ +void R_NewGame (void) +{ + int i; + + //clear playertexture pointers (the textures themselves were freed by texmgr_newgame) + for (i=0; ientities); + if (!data) + return; // error + if (com_token[0] != '{') + return; // error + + while (1) + { + data = COM_Parse(data); + if (!data) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + q_strlcpy(key, com_token + 1, sizeof(key)); + else + q_strlcpy(key, com_token, sizeof(key)); + while (key[0] && key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_ParseEx(data, CPE_ALLOWTRUNC); + if (!data) + return; // error + q_strlcpy(value, com_token, sizeof(value)); + + if (!strcmp("wateralpha", key)) + map_wateralpha = atof(value); + + if (!strcmp("lavaalpha", key)) + map_lavaalpha = atof(value); + + if (!strcmp("telealpha", key)) + map_telealpha = atof(value); + + if (!strcmp("slimealpha", key)) + map_slimealpha = atof(value); + } +} + + +/* +=============== +R_NewMap +=============== +*/ +void R_NewMap (void) +{ + int i; + + for (i=0 ; i<256 ; i++) + d_lightstylevalue[i] = 264; // normal light value + +// clear out efrags in case the level hasn't been reloaded +// FIXME: is this one short? + for (i=0 ; inumleafs ; i++) + cl.worldmodel->leafs[i].efrags = NULL; + + r_viewleaf = NULL; + R_ClearParticles (); + + GL_BuildLightmaps (); + GL_BuildBModelVertexBuffer (); + //ericw -- no longer load alias models into a VBO here, it's done in Mod_LoadAliasModel + + r_framecount = 0; //johnfitz -- paranoid? + r_visframecount = 0; //johnfitz -- paranoid? + + Sky_NewMap (); //johnfitz -- skybox in worldspawn + Fog_NewMap (); //johnfitz -- global fog in worldspawn + R_ParseWorldspawn (); //ericw -- wateralpha, lavaalpha, telealpha, slimealpha in worldspawn + + load_subdivide_size = gl_subdivide_size.value; //johnfitz -- is this the right place to set this? +} + +/* +==================== +R_TimeRefresh_f + +For program optimization +==================== +*/ +void R_TimeRefresh_f (void) +{ + int i; + float start, stop, time; + + if (cls.state != ca_connected) + { + Con_Printf("Not connected to a server\n"); + return; + } + + start = Sys_DoubleTime (); + for (i = 0; i < 128; i++) + { + GL_BeginRendering(&glx, &gly, &glwidth, &glheight); + r_refdef.viewangles[1] = i/128.0*360.0; + R_RenderView (); + GL_EndRendering (); + } + + glFinish (); + stop = Sys_DoubleTime (); + time = stop-start; + Con_Printf ("%f seconds (%f fps)\n", time, 128/time); +} + +void D_FlushCaches (void) +{ +} + +static GLuint gl_programs[16]; +static int gl_num_programs; + +static qboolean GL_CheckShader (GLuint shader) +{ + GLint status; + GL_GetShaderivFunc (shader, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) + { + char infolog[1024]; + + memset(infolog, 0, sizeof(infolog)); + GL_GetShaderInfoLogFunc (shader, sizeof(infolog), NULL, infolog); + + Con_Warning ("GLSL program failed to compile: %s", infolog); + + return false; + } + return true; +} + +static qboolean GL_CheckProgram (GLuint program) +{ + GLint status; + GL_GetProgramivFunc (program, GL_LINK_STATUS, &status); + + if (status != GL_TRUE) + { + char infolog[1024]; + + memset(infolog, 0, sizeof(infolog)); + GL_GetProgramInfoLogFunc (program, sizeof(infolog), NULL, infolog); + + Con_Warning ("GLSL program failed to link: %s", infolog); + + return false; + } + return true; +} + +/* +============= +GL_GetUniformLocation +============= +*/ +GLint GL_GetUniformLocation (GLuint *programPtr, const char *name) +{ + GLint location; + + if (!programPtr) + return -1; + + location = GL_GetUniformLocationFunc(*programPtr, name); + if (location == -1) + { + Con_Warning("GL_GetUniformLocationFunc %s failed\n", name); + *programPtr = 0; + } + return location; +} + +/* +==================== +GL_CreateProgram + +Compiles and returns GLSL program. +==================== +*/ +GLuint GL_CreateProgram (const GLchar *vertSource, const GLchar *fragSource, int numbindings, const glsl_attrib_binding_t *bindings) +{ + int i; + GLuint program, vertShader, fragShader; + + if (!gl_glsl_able) + return 0; + + vertShader = GL_CreateShaderFunc (GL_VERTEX_SHADER); + GL_ShaderSourceFunc (vertShader, 1, &vertSource, NULL); + GL_CompileShaderFunc (vertShader); + if (!GL_CheckShader (vertShader)) + { + GL_DeleteShaderFunc (vertShader); + return 0; + } + + fragShader = GL_CreateShaderFunc (GL_FRAGMENT_SHADER); + GL_ShaderSourceFunc (fragShader, 1, &fragSource, NULL); + GL_CompileShaderFunc (fragShader); + if (!GL_CheckShader (fragShader)) + { + GL_DeleteShaderFunc (vertShader); + GL_DeleteShaderFunc (fragShader); + return 0; + } + + program = GL_CreateProgramFunc (); + GL_AttachShaderFunc (program, vertShader); + GL_DeleteShaderFunc (vertShader); + GL_AttachShaderFunc (program, fragShader); + GL_DeleteShaderFunc (fragShader); + + for (i = 0; i < numbindings; i++) + { + GL_BindAttribLocationFunc (program, bindings[i].attrib, bindings[i].name); + } + + GL_LinkProgramFunc (program); + + if (!GL_CheckProgram (program)) + { + GL_DeleteProgramFunc (program); + return 0; + } + else + { + if (gl_num_programs == Q_COUNTOF(gl_programs)) + Host_Error ("gl_programs overflow"); + + gl_programs[gl_num_programs] = program; + gl_num_programs++; + + return program; + } +} + +/* +==================== +R_DeleteShaders + +Deletes any GLSL programs that have been created. +==================== +*/ +void R_DeleteShaders (void) +{ + int i; + + if (!gl_glsl_able) + return; + + for (i = 0; i < gl_num_programs; i++) + { + GL_DeleteProgramFunc (gl_programs[i]); + gl_programs[i] = 0; + } + gl_num_programs = 0; +} + +static GLuint current_array_buffer, current_element_array_buffer; + +/* +==================== +GL_BindBuffer + +glBindBuffer wrapper +==================== +*/ +void GL_BindBuffer (GLenum target, GLuint buffer) +{ + GLuint *cache; + + if (!gl_vbo_able) + return; + + switch (target) + { + case GL_ARRAY_BUFFER: + cache = ¤t_array_buffer; + break; + case GL_ELEMENT_ARRAY_BUFFER: + cache = ¤t_element_array_buffer; + break; + default: + Host_Error("GL_BindBuffer: unsupported target %d", (int)target); + return; + } + + if (*cache != buffer) + { + *cache = buffer; + GL_BindBufferFunc (target, *cache); + } +} + +/* +==================== +GL_ClearBufferBindings + +This must be called if you do anything that could make the cached bindings +invalid (e.g. manually binding, destroying the context). +==================== +*/ +void GL_ClearBufferBindings (void) +{ + if (!gl_vbo_able) + return; + + current_array_buffer = 0; + current_element_array_buffer = 0; + GL_BindBufferFunc (GL_ARRAY_BUFFER, 0); + GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, 0); +} diff --git a/Quake/gl_screen.cpp b/Quake/gl_screen.cpp new file mode 100644 index 0000000..8e4b4df --- /dev/null +++ b/Quake/gl_screen.cpp @@ -0,0 +1,1116 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// screen.c -- master for refresh, status bar, console, chat, notify, etc + +#include "quakedef.hpp" + +/* + +background clear +rendering +turtle/net/ram icons +sbar +centerprint / slow centerprint +notify lines +intermission / finale overlay +loading plaque +console +menu + +required background clears +required update regions + + +syncronous draw mode or async +One off screen buffer, with updates either copied or xblited +Need to double buffer? + + +async draw will require the refresh area to be cleared, because it will be +xblited, but sync draw can just ignore it. + +sync +draw + +CenterPrint () +SlowPrint () +Screen_Update (); +Con_Printf (); + +net +turn off messages option + +the refresh is allways rendered, unless the console is full screen + + +console is: + notify lines + half + full + +*/ + + +int glx, gly, glwidth, glheight; + +float scr_con_current; +float scr_conlines; // lines of console to display + +//johnfitz -- new cvars +cvar_t scr_menuscale = {"scr_menuscale", "1", CVAR_ARCHIVE}; +cvar_t scr_sbarscale = {"scr_sbarscale", "1", CVAR_ARCHIVE}; +cvar_t scr_sbaralpha = {"scr_sbaralpha", "0.75", CVAR_ARCHIVE}; +cvar_t scr_conwidth = {"scr_conwidth", "0", CVAR_ARCHIVE}; +cvar_t scr_conscale = {"scr_conscale", "1", CVAR_ARCHIVE}; +cvar_t scr_crosshairscale = {"scr_crosshairscale", "1", CVAR_ARCHIVE}; +cvar_t scr_showfps = {"scr_showfps", "0", CVAR_NONE}; +cvar_t scr_clock = {"scr_clock", "0", CVAR_NONE}; +//johnfitz +cvar_t scr_usekfont = {"scr_usekfont", "0", CVAR_NONE}; // 2021 re-release + +cvar_t scr_viewsize = {"viewsize","100", CVAR_ARCHIVE}; +cvar_t scr_fov = {"fov","90",CVAR_NONE}; // 10 - 170 +cvar_t scr_fov_adapt = {"fov_adapt","1",CVAR_ARCHIVE}; +cvar_t scr_conspeed = {"scr_conspeed","500",CVAR_ARCHIVE}; +cvar_t scr_centertime = {"scr_centertime","2",CVAR_NONE}; +cvar_t scr_showturtle = {"showturtle","0",CVAR_NONE}; +cvar_t scr_showpause = {"showpause","1",CVAR_NONE}; +cvar_t scr_printspeed = {"scr_printspeed","8",CVAR_NONE}; +cvar_t gl_triplebuffer = {"gl_triplebuffer", "1", CVAR_ARCHIVE}; + +cvar_t cl_gun_fovscale = {"cl_gun_fovscale","1",CVAR_ARCHIVE}; // Qrack + +extern cvar_t crosshair; + +qboolean scr_initialized; // ready to draw + +qpic_t *scr_net; +qpic_t *scr_turtle; + +int clearconsole; +int clearnotify; + +vrect_t scr_vrect; + +qboolean scr_disabled_for_loading; +qboolean scr_drawloading; +float scr_disabled_time; + +int scr_tileclear_updates = 0; //johnfitz + +void SCR_ScreenShot_f (void); + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + +char scr_centerstring[1024]; +float scr_centertime_start; // for slow victory printing +float scr_centertime_off; +int scr_center_lines; +int scr_erase_lines; +int scr_erase_center; + +/* +============== +SCR_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void SCR_CenterPrint (const char *str) //update centerprint data +{ + strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1); + scr_centertime_off = scr_centertime.value; + scr_centertime_start = cl.time; + +// count the number of lines for centering + scr_center_lines = 1; + str = scr_centerstring; + while (*str) + { + if (*str == '\n') + scr_center_lines++; + str++; + } +} + +void SCR_DrawCenterString (void) //actually do the drawing +{ + char *start; + int l; + int j; + int x, y; + int remaining; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + +// the finale prints the characters one at a time + if (cl.intermission) + remaining = scr_printspeed.value * (cl.time - scr_centertime_start); + else + remaining = 9999; + + scr_erase_center = 0; + start = scr_centerstring; + + if (scr_center_lines <= 4) + y = 200*0.35; //johnfitz -- 320x200 coordinate system + else + y = 48; + if (crosshair.value) + y -= 8; + + do + { + // scan the width of the line + for (l=0 ; l<40 ; l++) + if (start[l] == '\n' || !start[l]) + break; + x = (320 - l*8)/2; //johnfitz -- 320x200 coordinate system + for (j=0 ; j scr_erase_lines) + scr_erase_lines = scr_center_lines; + + scr_centertime_off -= host_frametime; + + if (scr_centertime_off <= 0 && !cl.intermission) + return; + if (key_dest != key_game) + return; + if (cl.paused) //johnfitz -- don't show centerprint during a pause + return; + + SCR_DrawCenterString (); +} + +//============================================================================= + +/* +==================== +AdaptFovx +Adapt a 4:3 horizontal FOV to the current screen size using the "Hor+" scaling: +2.0 * atan(width / height * 3.0 / 4.0 * tan(fov_x / 2.0)) +==================== +*/ +float AdaptFovx (float fov_x, float width, float height) +{ + float a, x; + + if (fov_x < 1 || fov_x > 179) + Sys_Error ("Bad fov: %f", fov_x); + + if (!scr_fov_adapt.value) + return fov_x; + if ((x = height / width) == 0.75) + return fov_x; + a = atan(0.75 / x * tan(fov_x / 360 * M_PI)); + a = a * 360 / M_PI; + return a; +} + +/* +==================== +CalcFovy +==================== +*/ +float CalcFovy (float fov_x, float width, float height) +{ + float a, x; + + if (fov_x < 1 || fov_x > 179) + Sys_Error ("Bad fov: %f", fov_x); + + x = width / tan(fov_x / 360 * M_PI); + a = atan(height / x); + a = a * 360 / M_PI; + return a; +} + +/* +================= +SCR_CalcRefdef + +Must be called whenever vid changes +Internal use only +================= +*/ +static void SCR_CalcRefdef (void) +{ + float size, scale; //johnfitz -- scale + +// force the status bar to redraw + Sbar_Changed (); + + scr_tileclear_updates = 0; //johnfitz + +// bound viewsize + if (scr_viewsize.value < 30) + Cvar_SetQuick (&scr_viewsize, "30"); + if (scr_viewsize.value > 120) + Cvar_SetQuick (&scr_viewsize, "120"); + +// bound fov + if (scr_fov.value < 10) + Cvar_SetQuick (&scr_fov, "10"); + if (scr_fov.value > 170) + Cvar_SetQuick (&scr_fov, "170"); + + vid.recalc_refdef = 0; + + //johnfitz -- rewrote this section + size = scr_viewsize.value; + scale = CLAMP (1.0f, scr_sbarscale.value, (float)glwidth / 320.0f); + + if (size >= 120 || cl.intermission || scr_sbaralpha.value < 1) //johnfitz -- scr_sbaralpha.value + sb_lines = 0; + else if (size >= 110) + sb_lines = 24 * scale; + else + sb_lines = 48 * scale; + + size = q_min(scr_viewsize.value, 100.f) / 100; + //johnfitz + + //johnfitz -- rewrote this section + r_refdef.vrect.width = q_max(glwidth * size, 96.0f); //no smaller than 96, for icons + r_refdef.vrect.height = q_min((int)(glheight * size), glheight - sb_lines); //make room for sbar + r_refdef.vrect.x = (glwidth - r_refdef.vrect.width)/2; + r_refdef.vrect.y = (glheight - sb_lines - r_refdef.vrect.height)/2; + //johnfitz + + r_refdef.fov_x = AdaptFovx(scr_fov.value, vid.width, vid.height); + r_refdef.fov_y = CalcFovy (r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height); + + scr_vrect = r_refdef.vrect; +} + + +/* +================= +SCR_SizeUp_f + +Keybinding command +================= +*/ +void SCR_SizeUp_f (void) +{ + Cvar_SetValueQuick (&scr_viewsize, scr_viewsize.value+10); +} + + +/* +================= +SCR_SizeDown_f + +Keybinding command +================= +*/ +void SCR_SizeDown_f (void) +{ + Cvar_SetValueQuick (&scr_viewsize, scr_viewsize.value-10); +} + +static void SCR_Callback_refdef (cvar_t *var) +{ + vid.recalc_refdef = 1; +} + +/* +================== +SCR_Conwidth_f -- johnfitz -- called when scr_conwidth or scr_conscale changes +================== +*/ +void SCR_Conwidth_f (cvar_t *var) +{ + vid.recalc_refdef = 1; + vid.conwidth = (scr_conwidth.value > 0) ? (int)scr_conwidth.value : (scr_conscale.value > 0) ? (int)(vid.width/scr_conscale.value) : vid.width; + vid.conwidth = CLAMP (320, vid.conwidth, vid.width); + vid.conwidth &= 0xFFFFFFF8; + vid.conheight = vid.conwidth * vid.height / vid.width; +} + +//============================================================================ + +/* +================== +SCR_LoadPics -- johnfitz +================== +*/ +void SCR_LoadPics (void) +{ + scr_net = Draw_PicFromWad ("net"); + scr_turtle = Draw_PicFromWad ("turtle"); +} + +/* +================== +SCR_Init +================== +*/ +void SCR_Init (void) +{ + //johnfitz -- new cvars + Cvar_RegisterVariable (&scr_menuscale); + Cvar_RegisterVariable (&scr_sbarscale); + Cvar_SetCallback (&scr_sbaralpha, SCR_Callback_refdef); + Cvar_RegisterVariable (&scr_sbaralpha); + Cvar_SetCallback (&scr_conwidth, &SCR_Conwidth_f); + Cvar_SetCallback (&scr_conscale, &SCR_Conwidth_f); + Cvar_RegisterVariable (&scr_conwidth); + Cvar_RegisterVariable (&scr_conscale); + Cvar_RegisterVariable (&scr_crosshairscale); + Cvar_RegisterVariable (&scr_showfps); + Cvar_RegisterVariable (&scr_clock); + //johnfitz + Cvar_RegisterVariable (&scr_usekfont); // 2021 re-release + Cvar_SetCallback (&scr_fov, SCR_Callback_refdef); + Cvar_SetCallback (&scr_fov_adapt, SCR_Callback_refdef); + Cvar_SetCallback (&scr_viewsize, SCR_Callback_refdef); + Cvar_RegisterVariable (&scr_fov); + Cvar_RegisterVariable (&scr_fov_adapt); + Cvar_RegisterVariable (&scr_viewsize); + Cvar_RegisterVariable (&scr_conspeed); + Cvar_RegisterVariable (&scr_showturtle); + Cvar_RegisterVariable (&scr_showpause); + Cvar_RegisterVariable (&scr_centertime); + Cvar_RegisterVariable (&scr_printspeed); + Cvar_RegisterVariable (&gl_triplebuffer); + Cvar_RegisterVariable (&cl_gun_fovscale); + + Cmd_AddCommand ("screenshot",SCR_ScreenShot_f); + Cmd_AddCommand ("sizeup",SCR_SizeUp_f); + Cmd_AddCommand ("sizedown",SCR_SizeDown_f); + + SCR_LoadPics (); //johnfitz + + scr_initialized = true; +} + +//============================================================================ + +/* +============== +SCR_DrawFPS -- johnfitz +============== +*/ +void SCR_DrawFPS (void) +{ + static double oldtime = 0; + static double lastfps = 0; + static int oldframecount = 0; + double elapsed_time; + int frames; + + elapsed_time = realtime - oldtime; + frames = r_framecount - oldframecount; + + if (elapsed_time < 0 || frames < 0) + { + oldtime = realtime; + oldframecount = r_framecount; + return; + } + // update value every 3/4 second + if (elapsed_time > 0.75) + { + lastfps = frames / elapsed_time; + oldtime = realtime; + oldframecount = r_framecount; + } + + if (scr_showfps.value) + { + char st[16]; + int x, y; + sprintf (st, "%4.0f fps", lastfps); + x = 320 - (strlen(st)<<3); + y = 200 - 8; + if (scr_clock.value) y -= 8; //make room for clock + GL_SetCanvas (CANVAS_BOTTOMRIGHT); + Draw_String (x, y, st); + scr_tileclear_updates = 0; + } +} + +/* +============== +SCR_DrawClock -- johnfitz +============== +*/ +void SCR_DrawClock (void) +{ + char str[12]; + + if (scr_clock.value == 1) + { + int minutes, seconds; + + minutes = cl.time / 60; + seconds = ((int)cl.time)%60; + + sprintf (str,"%i:%i%i", minutes, seconds/10, seconds%10); + } + else + return; + + //draw it + GL_SetCanvas (CANVAS_BOTTOMRIGHT); + Draw_String (320 - (strlen(str)<<3), 200 - 8, str); + + scr_tileclear_updates = 0; +} + +/* +============== +SCR_DrawDevStats +============== +*/ +void SCR_DrawDevStats (void) +{ + char str[40]; + int y = 25-9; //9=number of lines to print + int x = 0; //margin + + if (!devstats.value) + return; + + GL_SetCanvas (CANVAS_BOTTOMLEFT); + + Draw_Fill (x, y*8, 19*8, 9*8, 0, 0.5); //dark rectangle + + sprintf (str, "devstats |Curr Peak"); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "---------+---------"); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Edicts |%4i %4i", dev_stats.edicts, dev_peakstats.edicts); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Packet |%4i %4i", dev_stats.packetsize, dev_peakstats.packetsize); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Visedicts|%4i %4i", dev_stats.visedicts, dev_peakstats.visedicts); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Efrags |%4i %4i", dev_stats.efrags, dev_peakstats.efrags); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Dlights |%4i %4i", dev_stats.dlights, dev_peakstats.dlights); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Beams |%4i %4i", dev_stats.beams, dev_peakstats.beams); + Draw_String (x, (y++)*8-x, str); + + sprintf (str, "Tempents |%4i %4i", dev_stats.tempents, dev_peakstats.tempents); + Draw_String (x, (y++)*8-x, str); +} + +/* +============== +SCR_DrawTurtle +============== +*/ +void SCR_DrawTurtle (void) +{ + static int count; + + if (!scr_showturtle.value) + return; + + if (host_frametime < 0.1) + { + count = 0; + return; + } + + count++; + if (count < 3) + return; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + Draw_Pic (scr_vrect.x, scr_vrect.y, scr_turtle); +} + +/* +============== +SCR_DrawNet +============== +*/ +void SCR_DrawNet (void) +{ + if (realtime - cl.last_received_message < 0.3) + return; + if (cls.demoplayback) + return; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + Draw_Pic (scr_vrect.x+64, scr_vrect.y, scr_net); +} + +/* +============== +DrawPause +============== +*/ +void SCR_DrawPause (void) +{ + qpic_t *pic; + + if (!cl.paused) + return; + + if (!scr_showpause.value) // turn off for screenshots + return; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/pause.lmp"); + Draw_Pic ( (320 - pic->width)/2, (240 - 48 - pic->height)/2, pic); //johnfitz -- stretched menus + + scr_tileclear_updates = 0; //johnfitz +} + +/* +============== +SCR_DrawLoading +============== +*/ +void SCR_DrawLoading (void) +{ + qpic_t *pic; + + if (!scr_drawloading) + return; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/loading.lmp"); + Draw_Pic ( (320 - pic->width)/2, (240 - 48 - pic->height)/2, pic); //johnfitz -- stretched menus + + scr_tileclear_updates = 0; //johnfitz +} + +/* +============== +SCR_DrawCrosshair -- johnfitz +============== +*/ +void SCR_DrawCrosshair (void) +{ + if (!crosshair.value) + return; + + GL_SetCanvas (CANVAS_CROSSHAIR); + Draw_Character (-4, -4, '+'); //0,0 is center of viewport +} + + + +//============================================================================= + + +/* +================== +SCR_SetUpToDrawConsole +================== +*/ +void SCR_SetUpToDrawConsole (void) +{ + //johnfitz -- let's hack away the problem of slow console when host_timescale is <0 + extern cvar_t host_timescale; + float timescale, conspeed; + //johnfitz + + Con_CheckResize (); + + if (scr_drawloading) + return; // never a console with loading plaque + +// decide on the height of the console + con_forcedup = !cl.worldmodel || cls.signon != SIGNONS; + + if (con_forcedup) + { + scr_conlines = glheight; //full screen //johnfitz -- glheight instead of vid.height + scr_con_current = scr_conlines; + } + else if (key_dest == key_console) + scr_conlines = glheight/2; //half screen //johnfitz -- glheight instead of vid.height + else + scr_conlines = 0; //none visible + + timescale = (host_timescale.value > 0) ? host_timescale.value : 1; //johnfitz -- timescale + conspeed = (scr_conspeed.value > 0) ? scr_conspeed.value : 1e6f; + + if (scr_conlines < scr_con_current) + { + // ericw -- (glheight/600.0) factor makes conspeed resolution independent, using 800x600 as a baseline + scr_con_current -= conspeed*(glheight/600.0)*host_frametime/timescale; //johnfitz -- timescale + if (scr_conlines > scr_con_current) + scr_con_current = scr_conlines; + } + else if (scr_conlines > scr_con_current) + { + // ericw -- (glheight/600.0) + scr_con_current += conspeed*(glheight/600.0)*host_frametime/timescale; //johnfitz -- timescale + if (scr_conlines < scr_con_current) + scr_con_current = scr_conlines; + } + + if (clearconsole++ < vid.numpages) + Sbar_Changed (); + + if (!con_forcedup && scr_con_current) + scr_tileclear_updates = 0; //johnfitz +} + +/* +================== +SCR_DrawConsole +================== +*/ +void SCR_DrawConsole (void) +{ + if (scr_con_current) + { + Con_DrawConsole (scr_con_current, true); + clearconsole = 0; + } + else + { + if (key_dest == key_game || key_dest == key_message) + Con_DrawNotify (); // only draw notify in game + } +} + + +/* +============================================================================== + +SCREEN SHOTS + +============================================================================== +*/ + +static void SCR_ScreenShot_Usage (void) +{ + Con_Printf ("usage: screenshot \n"); + Con_Printf (" format must be \"png\" or \"tga\" or \"jpg\"\n"); + Con_Printf (" quality must be 1-100\n"); + return; +} + +/* +================== +SCR_ScreenShot_f -- johnfitz -- rewritten to use Image_WriteTGA +================== +*/ +void SCR_ScreenShot_f (void) +{ + byte *buffer; + char ext[4]; + char imagename[16]; //johnfitz -- was [80] + char checkname[MAX_OSPATH]; + int i, quality; + qboolean ok; + + Q_strncpy (ext, "png", sizeof(ext)); + + if (Cmd_Argc () >= 2) + { + const char *requested_ext = Cmd_Argv (1); + + if (!q_strcasecmp ("png", requested_ext) + || !q_strcasecmp ("tga", requested_ext) + || !q_strcasecmp ("jpg", requested_ext)) + Q_strncpy (ext, requested_ext, sizeof(ext)); + else + { + SCR_ScreenShot_Usage (); + return; + } + } + +// read quality as the 3rd param (only used for JPG) + quality = 90; + if (Cmd_Argc () >= 3) + quality = Q_atoi (Cmd_Argv(2)); + if (quality < 1 || quality > 100) + { + SCR_ScreenShot_Usage (); + return; + } + +// find a file name to save it to + for (i=0; i<10000; i++) + { + q_snprintf (imagename, sizeof(imagename), "spasm%04i.%s", i, ext); // "fitz%04i.tga" + q_snprintf (checkname, sizeof(checkname), "%s/%s", com_gamedir, imagename); + if (Sys_FileType(checkname) == FS_ENT_NONE) + break; // file doesn't exist + } + if (i == 10000) + { + Con_Printf ("SCR_ScreenShot_f: Couldn't find an unused filename\n"); + return; + } + +//get data + if (!(buffer = (byte *) malloc(glwidth*glheight*3))) + { + Con_Printf ("SCR_ScreenShot_f: Couldn't allocate memory\n"); + return; + } + + glPixelStorei (GL_PACK_ALIGNMENT, 1);/* for widths that aren't a multiple of 4 */ + glReadPixels (glx, gly, glwidth, glheight, GL_RGB, GL_UNSIGNED_BYTE, buffer); + +// now write the file + if (!q_strncasecmp (ext, "png", sizeof(ext))) + ok = Image_WritePNG (imagename, buffer, glwidth, glheight, 24, false); + else if (!q_strncasecmp (ext, "tga", sizeof(ext))) + ok = Image_WriteTGA (imagename, buffer, glwidth, glheight, 24, false); + else if (!q_strncasecmp (ext, "jpg", sizeof(ext))) + ok = Image_WriteJPG (imagename, buffer, glwidth, glheight, 24, quality, false); + else + ok = false; + + if (ok) + Con_Printf ("Wrote %s\n", imagename); + else + Con_Printf ("SCR_ScreenShot_f: Couldn't create %s\n", imagename); + + free (buffer); +} + + +//============================================================================= + + +/* +=============== +SCR_BeginLoadingPlaque + +================ +*/ +void SCR_BeginLoadingPlaque (void) +{ + S_StopAllSounds (true); + + if (cls.state != ca_connected) + return; + if (cls.signon != SIGNONS) + return; + +// redraw with no console and the loading plaque + Con_ClearNotify (); + scr_centertime_off = 0; + scr_con_current = 0; + + scr_drawloading = true; + Sbar_Changed (); + SCR_UpdateScreen (); + scr_drawloading = false; + + scr_disabled_for_loading = true; + scr_disabled_time = realtime; +} + +/* +=============== +SCR_EndLoadingPlaque + +================ +*/ +void SCR_EndLoadingPlaque (void) +{ + scr_disabled_for_loading = false; + Con_ClearNotify (); +} + +//============================================================================= + +const char *scr_notifystring; +qboolean scr_drawdialog; + +void SCR_DrawNotifyString (void) +{ + const char *start; + int l; + int j; + int x, y; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + start = scr_notifystring; + + y = 200 * 0.35; //johnfitz -- stretched overlays + + do + { + // scan the width of the line + for (l=0 ; l<40 ; l++) + if (start[l] == '\n' || !start[l]) + break; + x = (320 - l*8)/2; //johnfitz -- stretched overlays + for (j=0 ; j time1) + return false; + //johnfitz + + return (lastchar == 'y' || lastchar == 'Y' || lastkey == K_ABUTTON); +} + + +//============================================================================= + +//johnfitz -- deleted SCR_BringDownConsole + + +/* +================== +SCR_TileClear +johnfitz -- modified to use glwidth/glheight instead of vid.width/vid.height + also fixed the dimentions of right and top panels + also added scr_tileclear_updates +================== +*/ +void SCR_TileClear (void) +{ + //ericw -- added check for glsl gamma. TODO: remove this ugly optimization? + if (scr_tileclear_updates >= vid.numpages && !gl_clear.value && !(gl_glsl_gamma_able && vid_gamma.value != 1)) + return; + scr_tileclear_updates++; + + if (r_refdef.vrect.x > 0) + { + // left + Draw_TileClear (0, + 0, + r_refdef.vrect.x, + glheight - sb_lines); + // right + Draw_TileClear (r_refdef.vrect.x + r_refdef.vrect.width, + 0, + glwidth - r_refdef.vrect.x - r_refdef.vrect.width, + glheight - sb_lines); + } + + if (r_refdef.vrect.y > 0) + { + // top + Draw_TileClear (r_refdef.vrect.x, + 0, + r_refdef.vrect.width, + r_refdef.vrect.y); + // bottom + Draw_TileClear (r_refdef.vrect.x, + r_refdef.vrect.y + r_refdef.vrect.height, + r_refdef.vrect.width, + glheight - r_refdef.vrect.y - r_refdef.vrect.height - sb_lines); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. + +WARNING: be very careful calling this from elsewhere, because the refresh +needs almost the entire 256k of stack space! +================== +*/ +void SCR_UpdateScreen (void) +{ + vid.numpages = (gl_triplebuffer.value) ? 3 : 2; + + if (scr_disabled_for_loading) + { + if (realtime - scr_disabled_time > 60) + { + scr_disabled_for_loading = false; + Con_Printf ("load failed.\n"); + } + else + return; + } + + if (!scr_initialized || !con_initialized) + return; // not initialized yet + + + GL_BeginRendering (&glx, &gly, &glwidth, &glheight); + + // + // determine size of refresh window + // + if (vid.recalc_refdef) + SCR_CalcRefdef (); + +// +// do 3D refresh drawing, and then update the screen +// + SCR_SetUpToDrawConsole (); + + V_RenderView (); + + GL_Set2D (); + + //FIXME: only call this when needed + SCR_TileClear (); + + if (scr_drawdialog) //new game confirm + { + if (con_forcedup) + Draw_ConsoleBackground (); + else + Sbar_Draw (); + Draw_FadeScreen (); + SCR_DrawNotifyString (); + } + else if (scr_drawloading) //loading + { + SCR_DrawLoading (); + Sbar_Draw (); + } + else if (cl.intermission == 1 && key_dest == key_game) //end of level + { + Sbar_IntermissionOverlay (); + } + else if (cl.intermission == 2 && key_dest == key_game) //end of episode + { + Sbar_FinaleOverlay (); + SCR_CheckDrawCenterString (); + } + else + { + SCR_DrawCrosshair (); //johnfitz + SCR_DrawNet (); + SCR_DrawTurtle (); + SCR_DrawPause (); + SCR_CheckDrawCenterString (); + Sbar_Draw (); + SCR_DrawDevStats (); //johnfitz + SCR_DrawFPS (); //johnfitz + SCR_DrawClock (); //johnfitz + SCR_DrawConsole (); + M_Draw (); + } + + V_UpdateBlend (); //johnfitz -- V_UpdatePalette cleaned up and renamed + + GLSLGamma_GammaCorrect (); + + GL_EndRendering (); +} + diff --git a/Quake/gl_sky.cpp b/Quake/gl_sky.cpp new file mode 100644 index 0000000..28956ca --- /dev/null +++ b/Quake/gl_sky.cpp @@ -0,0 +1,1134 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +//gl_sky.c + +#include "quakedef.hpp" + +#define MAX_CLIP_VERTS 64 + +float Fog_GetDensity(void); +float *Fog_GetColor(void); + +extern int rs_skypolys; // for r_speeds readout +extern int rs_skypasses; // for r_speeds readout + +static float skyflatcolor[3]; +static float skymins[2][6], skymaxs[2][6]; + +static char skybox_name[1024]; //name of current skybox, or "" if no skybox + +static gltexture_t *skybox_textures[6]; +static gltexture_t *solidskytexture, *alphaskytexture; + +extern cvar_t gl_farclip; +static cvar_t r_fastsky = {"r_fastsky", "0", CVAR_NONE}; +static cvar_t r_sky_quality = {"r_sky_quality", "12", CVAR_NONE}; +static cvar_t r_skyalpha = {"r_skyalpha", "1", CVAR_NONE}; +static cvar_t r_skyfog = {"r_skyfog","0.5",CVAR_NONE}; + +static const int skytexorder[6] = {0,2,1,3,4,5}; //for skybox + +static const vec3_t skyclip[6] = { + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static const int st_to_vec[6][3] = +{ + {3,-1,2}, + {-3,1,2}, + {1,3,2}, + {-1,-3,2}, + {-2,-1,3}, // straight up + {2,-1,-3} // straight down +}; + +static const int vec_to_st[6][3] = +{ + {-2,3,1}, + {2,3,-1}, + {1,3,2}, + {-1,3,-2}, + {-2,-1,3}, + {-2,1,-3} +}; + +static float skyfog; // ericw + +//============================================================================== +// +// INIT +// +//============================================================================== + +/* +============= +Sky_LoadTexture + +A sky texture is 256*128, with the left side being a masked overlay +============== +*/ +void Sky_LoadTexture (qmodel_t *mod, texture_t *mt) +{ + char texturename[64]; + unsigned x, y, p, r, g, b, count, halfwidth, *rgba; + byte *src, *front_data, *back_data; + + if (mt->width != 256 || mt->height != 128) + { + Con_Warning ("Sky texture %s is %d x %d, expected 256 x 128\n", mt->name, mt->width, mt->height); + if (mt->width < 2 || mt->height < 1) + return; + } + + halfwidth = mt->width / 2; + back_data = (byte *) Hunk_AllocName (halfwidth*mt->height*2, "skytex"); + front_data = back_data + halfwidth*mt->height; + src = (byte *)(mt + 1); + +// extract back layer and upload + for (y=0 ; yheight ; y++) + memcpy (back_data + y*halfwidth, src + halfwidth + y*mt->width, halfwidth); + + q_snprintf(texturename, sizeof(texturename), "%s:%s_back", mod->name, mt->name); + solidskytexture = TexMgr_LoadImage (mod, texturename, halfwidth, mt->height, SRC_INDEXED, back_data, "", (src_offset_t)back_data, TEXPREF_NONE); + +// extract front layer and upload + r = g = b = count = 0; + for (y=0 ; yheight ; src+=mt->width, front_data+=halfwidth, y++) + { + for (x=0 ; xheight; + q_snprintf(texturename, sizeof(texturename), "%s:%s_front", mod->name, mt->name); + alphaskytexture = TexMgr_LoadImage (mod, texturename, halfwidth, mt->height, SRC_INDEXED, front_data, "", (src_offset_t)front_data, TEXPREF_ALPHA); + +// calculate r_fastsky color based on average of all opaque foreground colors + skyflatcolor[0] = (float)r/(count*255); + skyflatcolor[1] = (float)g/(count*255); + skyflatcolor[2] = (float)b/(count*255); +} + +/* +============= +Sky_LoadTextureQ64 + +Quake64 sky textures are 32*64 +============== +*/ +void Sky_LoadTextureQ64 (qmodel_t *mod, texture_t *mt) +{ + char texturename[64]; + unsigned i, p, r, g, b, count, halfheight, *rgba; + byte *front, *back, *front_rgba; + + if (mt->width != 32 || mt->height != 64) + { + Con_DWarning ("Q64 sky texture %s is %d x %d, expected 32 x 64\n", mt->name, mt->width, mt->height); + if (mt->width < 1 || mt->height < 2) + return; + } + + // pointers to both layer textures + halfheight = mt->height / 2; + front = (byte *)(mt+1); + back = (byte *)(mt+1) + mt->width*halfheight; + front_rgba = (byte *) Hunk_AllocName (4*mt->width*halfheight, "q64_skytex"); + + // Normal indexed texture for the back layer + q_snprintf(texturename, sizeof(texturename), "%s:%s_back", mod->name, mt->name); + solidskytexture = TexMgr_LoadImage (mod, texturename, mt->width, halfheight, SRC_INDEXED, back, "", (src_offset_t)back, TEXPREF_NONE); + + // front layer, convert to RGBA and upload + p = r = g = b = count = 0; + + for (i=mt->width*halfheight ; i!=0 ; i--) + { + rgba = &d_8to24table[*front++]; + + // RGB + front_rgba[p++] = ((byte*)rgba)[0]; + front_rgba[p++] = ((byte*)rgba)[1]; + front_rgba[p++] = ((byte*)rgba)[2]; + // Alpha + front_rgba[p++] = 128; // this look ok to me! + + // Fast sky + r += ((byte *)rgba)[0]; + g += ((byte *)rgba)[1]; + b += ((byte *)rgba)[2]; + count++; + } + + q_snprintf(texturename, sizeof(texturename), "%s:%s_front", mod->name, mt->name); + alphaskytexture = TexMgr_LoadImage (mod, texturename, mt->width, halfheight, SRC_RGBA, front_rgba, "", (src_offset_t)front_rgba, TEXPREF_ALPHA); + + // calculate r_fastsky color based on average of all opaque foreground colors + skyflatcolor[0] = (float)r/(count*255); + skyflatcolor[1] = (float)g/(count*255); + skyflatcolor[2] = (float)b/(count*255); +} + +/* +================== +Sky_LoadSkyBox +================== +*/ +static const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; +void Sky_LoadSkyBox (const char *name) +{ + int i, mark, width, height; + char filename[MAX_OSPATH]; + byte *data; + qboolean nonefound = true; + + if (strcmp(skybox_name, name) == 0) + return; //no change + + //purge old textures + for (i=0; i<6; i++) + { + if (skybox_textures[i] && skybox_textures[i] != notexture) + TexMgr_FreeTexture (skybox_textures[i]); + skybox_textures[i] = NULL; + } + + //turn off skybox if sky is set to "" + if (name[0] == 0) + { + skybox_name[0] = 0; + return; + } + + //load textures + for (i=0; i<6; i++) + { + mark = Hunk_LowMark (); + q_snprintf (filename, sizeof(filename), "gfx/env/%s%s", name, suf[i]); + data = Image_LoadImage (filename, &width, &height); + if (data) + { + skybox_textures[i] = TexMgr_LoadImage (cl.worldmodel, filename, width, height, SRC_RGBA, data, filename, 0, TEXPREF_NONE); + nonefound = false; + } + else + { + Con_Printf ("Couldn't load %s\n", filename); + skybox_textures[i] = notexture; + } + Hunk_FreeToLowMark (mark); + } + + if (nonefound) // go back to scrolling sky if skybox is totally missing + { + for (i=0; i<6; i++) + { + if (skybox_textures[i] && skybox_textures[i] != notexture) + TexMgr_FreeTexture (skybox_textures[i]); + skybox_textures[i] = NULL; + } + skybox_name[0] = 0; + return; + } + + q_strlcpy(skybox_name, name, sizeof(skybox_name)); +} + +/* +================= +Sky_ClearAll + +Called on map unload/game change to avoid keeping pointers to freed data +================= +*/ +void Sky_ClearAll (void) +{ + int i; + + skybox_name[0] = 0; + for (i=0; i<6; i++) + skybox_textures[i] = NULL; + solidskytexture = NULL; + alphaskytexture = NULL; +} + +/* +================= +Sky_NewMap +================= +*/ +void Sky_NewMap (void) +{ + char key[128], value[4096]; + const char *data; + + skyfog = r_skyfog.value; + + // + // read worldspawn (this is so ugly, and shouldn't it be done on the server?) + // + data = cl.worldmodel->entities; + if (!data) + return; //FIXME: how could this possibly ever happen? -- if there's no + // worldspawn then the sever wouldn't send the loadmap message to the client + + data = COM_Parse(data); + if (!data) //should never happen + return; // error + if (com_token[0] != '{') //should never happen + return; // error + while (1) + { + data = COM_Parse(data); + if (!data) + return; // error + if (com_token[0] == '}') + break; // end of worldspawn + if (com_token[0] == '_') + q_strlcpy(key, com_token + 1, sizeof(key)); + else + q_strlcpy(key, com_token, sizeof(key)); + while (key[0] && key[strlen(key)-1] == ' ') // remove trailing spaces + key[strlen(key)-1] = 0; + data = COM_ParseEx(data, CPE_ALLOWTRUNC); + if (!data) + return; // error + q_strlcpy(value, com_token, sizeof(value)); + + if (!strcmp("sky", key)) + Sky_LoadSkyBox(value); + + if (!strcmp("skyfog", key)) + skyfog = atof(value); +#if 1 /* also accept non-standard keys */ + else if (!strcmp("skyname", key)) //half-life + Sky_LoadSkyBox(value); + else if (!strcmp("qlsky", key)) //quake lives + Sky_LoadSkyBox(value); +#endif + } +} + +/* +================= +Sky_SkyCommand_f +================= +*/ +void Sky_SkyCommand_f (void) +{ + switch (Cmd_Argc()) + { + case 1: + Con_Printf("\"sky\" is \"%s\"\n", skybox_name); + break; + case 2: + Sky_LoadSkyBox(Cmd_Argv(1)); + break; + default: + Con_Printf("usage: sky \n"); + } +} + +/* +==================== +R_SetSkyfog_f -- ericw +==================== +*/ +static void R_SetSkyfog_f (cvar_t *var) +{ +// clear any skyfog setting from worldspawn + skyfog = var->value; +} + +/* +============= +Sky_Init +============= +*/ +void Sky_Init (void) +{ + int i; + + Cvar_RegisterVariable (&r_fastsky); + Cvar_RegisterVariable (&r_sky_quality); + Cvar_RegisterVariable (&r_skyalpha); + Cvar_RegisterVariable (&r_skyfog); + Cvar_SetCallback (&r_skyfog, R_SetSkyfog_f); + + Cmd_AddCommand ("sky",Sky_SkyCommand_f); + + skybox_name[0] = 0; + for (i=0; i<6; i++) + skybox_textures[i] = NULL; +} + +//============================================================================== +// +// PROCESS SKY SURFS +// +//============================================================================== + +/* +================= +Sky_ProjectPoly + +update sky bounds +================= +*/ +void Sky_ProjectPoly (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + + // 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]; + + 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 < skymins[0][axis]) + skymins[0][axis] = s; + if (t < skymins[1][axis]) + skymins[1][axis] = t; + if (s > skymaxs[0][axis]) + skymaxs[0][axis] = s; + if (t > skymaxs[1][axis]) + skymaxs[1][axis] = t; + } +} + +/* +================= +Sky_ClipPoly +================= +*/ +static void Sky_ClipPoly (int nump, vec3_t vecs, int stage) +{ + const float *norm; + float *v; + qboolean front, back; + float d, e; + int newc[2]; + int i, j; + + const int max_clip_verts = nump + 2; + const int on_heap = max_clip_verts > MAX_CLIP_VERTS; + int *sides; + float *dists; + vec3_t *newv_0; + vec3_t *newv_1; + + if (stage == 6) // fully clipped + { + Sky_ProjectPoly (nump, vecs); + return; + } + + front = back = false; + norm = skyclip[stage]; + + sides = (int *) (on_heap ? malloc(max_clip_verts * sizeof(int)) : alloca(max_clip_verts * sizeof(int))); + dists = (float *) (on_heap ? malloc(max_clip_verts * sizeof(float)) : alloca(max_clip_verts * sizeof(float))); + + 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 + Sky_ClipPoly (nump, vecs, stage+1); + if (on_heap) { + free(dists); + free(sides); + } + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + // 2-dim vec3_t newv[2][MAX_CLIP_VERTS]; as 2 arrays + newv_0 = (vec3_t *) (on_heap ? malloc(max_clip_verts * sizeof(vec3_t)) : alloca(max_clip_verts * sizeof(vec3_t))); + newv_1 = (vec3_t *) (on_heap ? malloc(max_clip_verts * sizeof(vec3_t)) : alloca(max_clip_verts * sizeof(vec3_t))); + + for (i=0, v = vecs ; inumverts + 2; + const int num_verts = p->numverts; + const int on_heap = max_clip_verts > MAX_CLIP_VERTS; + vec3_t *verts = (vec3_t *) (on_heap ? + malloc(max_clip_verts * sizeof(vec3_t)) : + alloca(max_clip_verts * sizeof(vec3_t))); + int i = 0; + + for ( ; i < num_verts; i++) { + VectorSubtract (p->verts[i], r_origin, verts[i]); + } + Sky_ClipPoly (num_verts, verts[0], 0); + + if (on_heap) free(verts); + } +} + +/* +================ +Sky_ProcessTextureChains -- handles sky polys in world model +================ +*/ +void Sky_ProcessTextureChains (void) +{ + int i; + msurface_t *s; + texture_t *t; + + if (!r_drawworld_cheatsafe) + return; + + for (i=0 ; inumtextures ; i++) + { + t = cl.worldmodel->textures[i]; + + if (!t || !t->texturechains[chain_world] || !(t->texturechains[chain_world]->flags & SURF_DRAWSKY)) + continue; + + for (s = t->texturechains[chain_world]; s; s = s->texturechain) + Sky_ProcessPoly (s->polys); + } +} + +/* +================ +Sky_ProcessEntities -- handles sky polys on brush models +================ +*/ +void Sky_ProcessEntities (void) +{ + entity_t *e; + msurface_t *s; + glpoly_t *p; + int i,j,k,mark; + float dot; + qboolean rotated; + vec3_t temp, forward, right, up; + + if (!r_drawentities.value) + return; + + for (i=0 ; imodel->type != mod_brush) + continue; + + if (R_CullModelForEntity(e)) + continue; + + if (e->alpha == ENTALPHA_ZERO) + continue; + + VectorSubtract (r_refdef.vieworg, e->origin, modelorg); + if (e->angles[0] || e->angles[1] || e->angles[2]) + { + rotated = true; + AngleVectors (e->angles, forward, right, up); + VectorCopy (modelorg, temp); + modelorg[0] = DotProduct (temp, forward); + modelorg[1] = -DotProduct (temp, right); + modelorg[2] = DotProduct (temp, up); + } + else + rotated = false; + + s = &e->model->surfaces[e->model->firstmodelsurface]; + + for (j=0 ; jmodel->nummodelsurfaces ; j++, s++) + { + if (s->flags & SURF_DRAWSKY) + { + dot = DotProduct (modelorg, s->plane->normal) - s->plane->dist; + if (((s->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || + (!(s->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + { + //copy the polygon and translate manually, since Sky_ProcessPoly needs it to be in world space + mark = Hunk_LowMark(); + p = (glpoly_t *) Hunk_Alloc (sizeof(*s->polys)); //FIXME: don't allocate for each poly + p->numverts = s->polys->numverts; + for (k=0; knumverts; k++) + { + if (rotated) + { + p->verts[k][0] = e->origin[0] + s->polys->verts[k][0] * forward[0] + - s->polys->verts[k][1] * right[0] + + s->polys->verts[k][2] * up[0]; + p->verts[k][1] = e->origin[1] + s->polys->verts[k][0] * forward[1] + - s->polys->verts[k][1] * right[1] + + s->polys->verts[k][2] * up[1]; + p->verts[k][2] = e->origin[2] + s->polys->verts[k][0] * forward[2] + - s->polys->verts[k][1] * right[2] + + s->polys->verts[k][2] * up[2]; + } + else + VectorAdd(s->polys->verts[k], e->origin, p->verts[k]); + } + Sky_ProcessPoly (p); + Hunk_FreeToLowMark (mark); + } + } + } + } +} + +//============================================================================== +// +// RENDER SKYBOX +// +//============================================================================== + +/* +============== +Sky_EmitSkyBoxVertex +============== +*/ +void Sky_EmitSkyBoxVertex (float s, float t, int axis) +{ + vec3_t v, b; + int j, k; + float w, h; + + b[0] = s * gl_farclip.value / sqrt(3.0); + b[1] = t * gl_farclip.value / sqrt(3.0); + b[2] = gl_farclip.value / sqrt(3.0); + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + v[j] = -b[-k - 1]; + else + v[j] = b[k - 1]; + v[j] += r_origin[j]; + } + + // convert from range [-1,1] to [0,1] + s = (s+1)*0.5; + t = (t+1)*0.5; + + // avoid bilerp seam + w = skybox_textures[skytexorder[axis]]->width; + h = skybox_textures[skytexorder[axis]]->height; + s = s * (w-1)/w + 0.5/w; + t = t * (h-1)/h + 0.5/h; + + t = 1.0 - t; + glTexCoord2f (s, t); + glVertex3fv (v); +} + +/* +============== +Sky_DrawSkyBox + +FIXME: eliminate cracks by adding an extra vert on tjuncs +============== +*/ +void Sky_DrawSkyBox (void) +{ + int i; + + for (i=0 ; i<6 ; i++) + { + if (skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i]) + continue; + + GL_Bind (skybox_textures[skytexorder[i]]); + +#if 1 /* FIXME: this is to avoid tjunctions until i can do it the right way */ + skymins[0][i] = -1; + skymins[1][i] = -1; + skymaxs[0][i] = 1; + skymaxs[1][i] = 1; +#endif + glBegin (GL_QUADS); + Sky_EmitSkyBoxVertex (skymins[0][i], skymins[1][i], i); + Sky_EmitSkyBoxVertex (skymins[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymins[1][i], i); + glEnd (); + + rs_skypolys++; + rs_skypasses++; + + if (Fog_GetDensity() > 0 && skyfog > 0) + { + float *c; + + c = Fog_GetColor(); + glEnable (GL_BLEND); + glDisable (GL_TEXTURE_2D); + glColor4f (c[0],c[1],c[2], CLAMP(0.0f,skyfog,1.0f)); + + glBegin (GL_QUADS); + Sky_EmitSkyBoxVertex (skymins[0][i], skymins[1][i], i); + Sky_EmitSkyBoxVertex (skymins[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymaxs[1][i], i); + Sky_EmitSkyBoxVertex (skymaxs[0][i], skymins[1][i], i); + glEnd (); + + glColor3f (1, 1, 1); + glEnable (GL_TEXTURE_2D); + glDisable (GL_BLEND); + + rs_skypasses++; + } + } +} + +//============================================================================== +// +// RENDER CLOUDS +// +//============================================================================== + +/* +============== +Sky_SetBoxVert +============== +*/ +void Sky_SetBoxVert (float s, float t, int axis, vec3_t v) +{ + vec3_t b; + int j, k; + + b[0] = s * gl_farclip.value / sqrt(3.0); + b[1] = t * gl_farclip.value / sqrt(3.0); + b[2] = gl_farclip.value / sqrt(3.0); + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + v[j] = -b[-k - 1]; + else + v[j] = b[k - 1]; + v[j] += r_origin[j]; + } +} + +/* +============= +Sky_GetTexCoord +============= +*/ +void Sky_GetTexCoord (vec3_t v, float speed, float *s, float *t) +{ + vec3_t dir; + float length, scroll; + + VectorSubtract (v, r_origin, dir); + dir[2] *= 3; // flatten the sphere + + length = dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2]; + length = sqrt (length); + length = 6*63/length; + + scroll = cl.time*speed; + scroll -= (int)scroll & ~127; + + *s = (scroll + dir[0] * length) * (1.0/128); + *t = (scroll + dir[1] * length) * (1.0/128); +} + +/* +=============== +Sky_DrawFaceQuad +=============== +*/ +void Sky_DrawFaceQuad (glpoly_t *p) +{ + float s, t; + float *v; + int i; + + if (gl_mtexable && r_skyalpha.value >= 1.0) + { + GL_Bind (solidskytexture); + GL_EnableMultitexture(); + GL_Bind (alphaskytexture); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + { + Sky_GetTexCoord (v, 8, &s, &t); + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, s, t); + Sky_GetTexCoord (v, 16, &s, &t); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, s, t); + glVertex3fv (v); + } + glEnd (); + + GL_DisableMultitexture(); + + rs_skypolys++; + rs_skypasses++; + } + else + { + GL_Bind (solidskytexture); + + if (r_skyalpha.value < 1.0) + glColor3f (1, 1, 1); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + { + Sky_GetTexCoord (v, 8, &s, &t); + glTexCoord2f (s, t); + glVertex3fv (v); + } + glEnd (); + + GL_Bind (alphaskytexture); + glEnable (GL_BLEND); + + if (r_skyalpha.value < 1.0) + glColor4f (1, 1, 1, r_skyalpha.value); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + { + Sky_GetTexCoord (v, 16, &s, &t); + glTexCoord2f (s, t); + glVertex3fv (v); + } + glEnd (); + + glDisable (GL_BLEND); + + rs_skypolys++; + rs_skypasses += 2; + } + + if (Fog_GetDensity() > 0 && skyfog > 0) + { + float *c; + + c = Fog_GetColor(); + glEnable (GL_BLEND); + glDisable (GL_TEXTURE_2D); + glColor4f (c[0],c[1],c[2], CLAMP(0.0f,skyfog,1.0f)); + + glBegin (GL_QUADS); + for (i=0, v=p->verts[0] ; i<4 ; i++, v+=VERTEXSIZE) + glVertex3fv (v); + glEnd (); + + glColor3f (1, 1, 1); + glEnable (GL_TEXTURE_2D); + glDisable (GL_BLEND); + + rs_skypasses++; + } +} + +/* +============== +Sky_DrawFace +============== +*/ + +void Sky_DrawFace (int axis) +{ + glpoly_t *p; + vec3_t verts[4]; + int i, j, start; + float di,qi,dj,qj; + vec3_t up, right, temp, temp2; + + Sky_SetBoxVert(-1.0, -1.0, axis, verts[0]); + Sky_SetBoxVert(-1.0, 1.0, axis, verts[1]); + Sky_SetBoxVert(1.0, 1.0, axis, verts[2]); + Sky_SetBoxVert(1.0, -1.0, axis, verts[3]); + + start = Hunk_LowMark (); + p = (glpoly_t *) Hunk_Alloc(sizeof(glpoly_t)); + + VectorSubtract(verts[2],verts[3],up); + VectorSubtract(verts[2],verts[1],right); + + di = q_max((int)r_sky_quality.value, 1); + qi = 1.0 / di; + dj = (axis < 4) ? di*2 : di; //subdivide vertically more than horizontally on skybox sides + qj = 1.0 / dj; + + for (i=0; i skymaxs[0][axis]/2+0.5 || + j*qj < skymins[1][axis]/2+0.5 - qj || j*qj > skymaxs[1][axis]/2+0.5) + continue; + + //if (i&1 ^ j&1) continue; //checkerboard test + VectorScale (right, qi*i, temp); + VectorScale (up, qj*j, temp2); + VectorAdd(temp,temp2,temp); + VectorAdd(verts[0],temp,p->verts[0]); + + VectorScale (up, qj, temp); + VectorAdd (p->verts[0],temp,p->verts[1]); + + VectorScale (right, qi, temp); + VectorAdd (p->verts[1],temp,p->verts[2]); + + VectorAdd (p->verts[0],temp,p->verts[3]); + + Sky_DrawFaceQuad (p); + } + } + Hunk_FreeToLowMark (start); +} + +/* +============== +Sky_DrawSkyLayers + +draws the old-style scrolling cloud layers +============== +*/ +void Sky_DrawSkyLayers (void) +{ + int i; + + if (r_skyalpha.value < 1.0) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + for (i=0 ; i<6 ; i++) + if (skymins[0][i] < skymaxs[0][i] && skymins[1][i] < skymaxs[1][i]) + Sky_DrawFace (i); + + if (r_skyalpha.value < 1.0) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +} + +/* +============== +Sky_DrawSky + +called once per frame before drawing anything else +============== +*/ +void Sky_DrawSky (void) +{ + int i; + + //in these special render modes, the sky faces are handled in the normal world/brush renderer + if (r_drawflat_cheatsafe || r_lightmap_cheatsafe) + return; + + // + // reset sky bounds + // + for (i=0 ; i<6 ; i++) + { + skymins[0][i] = skymins[1][i] = FLT_MAX; + skymaxs[0][i] = skymaxs[1][i] = -FLT_MAX; + } + + // + // process world and bmodels: draw flat-shaded sky surfs, and update skybounds + // + Fog_DisableGFog (); + glDisable (GL_TEXTURE_2D); + if (Fog_GetDensity() > 0) + glColor3fv (Fog_GetColor()); + else + glColor3fv (skyflatcolor); + Sky_ProcessTextureChains (); + Sky_ProcessEntities (); + glColor3f (1, 1, 1); + glEnable (GL_TEXTURE_2D); + + // + // render slow sky: cloud layers or skybox + // + if (!r_fastsky.value && !(Fog_GetDensity() > 0 && skyfog >= 1)) + { + glDepthFunc(GL_GEQUAL); + glDepthMask(0); + + if (skybox_name[0]) + Sky_DrawSkyBox (); + else + Sky_DrawSkyLayers (); + + glDepthMask(1); + glDepthFunc(GL_LEQUAL); + } + + Fog_EnableGFog (); +} diff --git a/Quake/gl_texmgr.cpp b/Quake/gl_texmgr.cpp new file mode 100644 index 0000000..6e4c531 --- /dev/null +++ b/Quake/gl_texmgr.cpp @@ -0,0 +1,1551 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +//gl_texmgr.c -- fitzquake's texture manager. manages opengl texture images + +#include "quakedef.hpp" + +static const int gl_solid_format = 3; +static const int gl_alpha_format = 4; + +static cvar_t gl_texturemode = {"gl_texturemode", "", CVAR_ARCHIVE}; +static cvar_t gl_texture_anisotropy = {"gl_texture_anisotropy", "1", CVAR_ARCHIVE}; +static cvar_t gl_max_size = {"gl_max_size", "0", CVAR_NONE}; +static cvar_t gl_picmip = {"gl_picmip", "0", CVAR_NONE}; +static GLint gl_hardware_maxsize; + +#define MAX_GLTEXTURES 4096 +static int numgltextures; +static gltexture_t *active_gltextures, *free_gltextures; +gltexture_t *notexture, *nulltexture; + +unsigned int d_8to24table[256]; +unsigned int d_8to24table_fbright[256]; +unsigned int d_8to24table_fbright_fence[256]; +unsigned int d_8to24table_nobright[256]; +unsigned int d_8to24table_nobright_fence[256]; +unsigned int d_8to24table_conchars[256]; + +/* +================================================================================ + + COMMANDS + +================================================================================ +*/ + +typedef struct +{ + int magfilter; + int minfilter; + const char *name; +} glmode_t; +static glmode_t glmodes[] = { + {GL_NEAREST, GL_NEAREST, "GL_NEAREST"}, + {GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, "GL_NEAREST_MIPMAP_NEAREST"}, + {GL_NEAREST, GL_NEAREST_MIPMAP_LINEAR, "GL_NEAREST_MIPMAP_LINEAR"}, + {GL_LINEAR, GL_LINEAR, "GL_LINEAR"}, + {GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST, "GL_LINEAR_MIPMAP_NEAREST"}, + {GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, "GL_LINEAR_MIPMAP_LINEAR"}, +}; +#define NUM_GLMODES (int)Q_COUNTOF(glmodes) +static int glmode_idx = NUM_GLMODES - 1; /* trilinear */ + +/* +=============== +TexMgr_DescribeTextureModes_f -- report available texturemodes +=============== +*/ +static void TexMgr_DescribeTextureModes_f (void) +{ + int i; + for (i = 0; i < NUM_GLMODES; i++) + Con_SafePrintf (" %2i: %s\n", i + 1, glmodes[i].name); + Con_Printf ("%i modes\n", i); +} + +/* +=============== +TexMgr_SetFilterModes +=============== +*/ +static void TexMgr_SetFilterModes (gltexture_t *glt) +{ + GL_Bind (glt); + + if (glt->flags & TEXPREF_NEAREST) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else if (glt->flags & TEXPREF_LINEAR) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + else if (glt->flags & TEXPREF_MIPMAP) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glmodes[glmode_idx].magfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glmodes[glmode_idx].minfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value); + } + else + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glmodes[glmode_idx].magfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glmodes[glmode_idx].magfilter); + } +} + +/* +=============== +TexMgr_TextureMode_f -- called when gl_texturemode changes +=============== +*/ +static void TexMgr_TextureMode_f (cvar_t *var) +{ + gltexture_t *glt; + int i; + + for (i = 0; i < NUM_GLMODES; i++) + { + if (!Q_strcmp (glmodes[i].name, gl_texturemode.string)) + { + if (glmode_idx != i) + { + glmode_idx = i; + for (glt = active_gltextures; glt; glt = glt->next) + TexMgr_SetFilterModes (glt); + Sbar_Changed (); //sbar graphics need to be redrawn with new filter mode + //FIXME: warpimages need to be redrawn, too. + } + return; + } + } + + for (i = 0; i < NUM_GLMODES; i++) + { + if (!q_strcasecmp (glmodes[i].name, gl_texturemode.string)) + { + Cvar_SetQuick (&gl_texturemode, glmodes[i].name); + return; + } + } + + i = atoi(gl_texturemode.string); + if (i >= 1 && i <= NUM_GLMODES) + { + Cvar_SetQuick (&gl_texturemode, glmodes[i-1].name); + return; + } + + Con_Printf ("\"%s\" is not a valid texturemode\n", gl_texturemode.string); + Cvar_SetQuick (&gl_texturemode, glmodes[glmode_idx].name); +} + +/* +=============== +TexMgr_Anisotropy_f -- called when gl_texture_anisotropy changes +=============== +*/ +static void TexMgr_Anisotropy_f (cvar_t *var) +{ + if (gl_texture_anisotropy.value < 1) + { + Cvar_SetQuick (&gl_texture_anisotropy, "1"); + } + else if (gl_texture_anisotropy.value > gl_max_anisotropy) + { + Cvar_SetValueQuick (&gl_texture_anisotropy, gl_max_anisotropy); + } + else + { + gltexture_t *glt; + for (glt = active_gltextures; glt; glt = glt->next) + { + /* TexMgr_SetFilterModes (glt);*/ + if (glt->flags & TEXPREF_MIPMAP) { + GL_Bind (glt); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glmodes[glmode_idx].magfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glmodes[glmode_idx].minfilter); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value); + } + } + } +} + +/* +=============== +TexMgr_Imagelist_f -- report loaded textures +=============== +*/ +static void TexMgr_Imagelist_f (void) +{ + float mb; + float texels = 0; + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + { + Con_SafePrintf (" %4i x%4i %s\n", glt->width, glt->height, glt->name); + if (glt->flags & TEXPREF_MIPMAP) + texels += glt->width * glt->height * 4.0f / 3.0f; + else + texels += (glt->width * glt->height); + } + + mb = texels * (Cvar_VariableValue("vid_bpp") / 8.0f) / 0x100000; + Con_Printf ("%i textures %i pixels %1.1f megabytes\n", numgltextures, (int)texels, mb); +} + +/* +=============== +TexMgr_Imagedump_f -- dump all current textures to TGA files +=============== +*/ +static void TexMgr_Imagedump_f (void) +{ + char tganame[MAX_OSPATH], tempname[MAX_OSPATH], dirname[MAX_OSPATH]; + gltexture_t *glt; + byte *buffer; + char *c; + + //create directory + q_snprintf(dirname, sizeof(dirname), "%s/imagedump", com_gamedir); + Sys_mkdir (dirname); + + //loop through textures + for (glt = active_gltextures; glt; glt = glt->next) + { + q_strlcpy (tempname, glt->name, sizeof(tempname)); + while ((c = strchr(tempname, ':')) != NULL) *c = '_'; + while ((c = strchr(tempname, '/')) != NULL) *c = '_'; + while ((c = strchr(tempname, '*')) != NULL) *c = '_'; + q_snprintf(tganame, sizeof(tganame), "imagedump/%s.tga", tempname); + + GL_Bind (glt); + glPixelStorei (GL_PACK_ALIGNMENT, 1);/* for widths that aren't a multiple of 4 */ + + if (glt->flags & TEXPREF_ALPHA) + { + buffer = (byte *) malloc(glt->width*glt->height*4); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + Image_WriteTGA (tganame, buffer, glt->width, glt->height, 32, true); + } + else + { + buffer = (byte *) malloc(glt->width*glt->height*3); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer); + Image_WriteTGA (tganame, buffer, glt->width, glt->height, 24, true); + } + free (buffer); + } + + Con_Printf ("dumped %i textures to %s\n", numgltextures, dirname); +} + +/* +=============== +TexMgr_FrameUsage -- report texture memory usage for this frame +=============== +*/ +float TexMgr_FrameUsage (void) +{ + float mb; + float texels = 0; + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->visframe == r_framecount) + { + if (glt->flags & TEXPREF_MIPMAP) + texels += glt->width * glt->height * 4.0f / 3.0f; + else + texels += (glt->width * glt->height); + } + } + + mb = texels * (Cvar_VariableValue("vid_bpp") / 8.0f) / 0x100000; + return mb; +} + +/* +================================================================================ + + TEXTURE MANAGER + +================================================================================ +*/ + +/* +================ +TexMgr_FindTexture +================ +*/ +gltexture_t *TexMgr_FindTexture (qmodel_t *owner, const char *name) +{ + gltexture_t *glt; + + if (name) + { + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->owner == owner && !strcmp (glt->name, name)) + return glt; + } + } + + return NULL; +} + +/* +================ +TexMgr_NewTexture +================ +*/ +gltexture_t *TexMgr_NewTexture (void) +{ + gltexture_t *glt; + + if (numgltextures == MAX_GLTEXTURES) + Sys_Error("numgltextures == MAX_GLTEXTURES\n"); + + glt = free_gltextures; + free_gltextures = glt->next; + glt->next = active_gltextures; + active_gltextures = glt; + + glGenTextures(1, &glt->texnum); + numgltextures++; + return glt; +} + +static void GL_DeleteTexture (gltexture_t *texture); + +//ericw -- workaround for preventing TexMgr_FreeTexture during TexMgr_ReloadImages +static qboolean in_reload_images; + +/* +================ +TexMgr_FreeTexture +================ +*/ +void TexMgr_FreeTexture (gltexture_t *kill) +{ + gltexture_t *glt; + + if (in_reload_images) + return; + + if (kill == NULL) + { + Con_Printf ("TexMgr_FreeTexture: NULL texture\n"); + return; + } + + if (active_gltextures == kill) + { + active_gltextures = kill->next; + kill->next = free_gltextures; + free_gltextures = kill; + + GL_DeleteTexture(kill); + numgltextures--; + return; + } + + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->next == kill) + { + glt->next = kill->next; + kill->next = free_gltextures; + free_gltextures = kill; + + GL_DeleteTexture(kill); + numgltextures--; + return; + } + } + + Con_Printf ("TexMgr_FreeTexture: not found\n"); +} + +/* +================ +TexMgr_FreeTextures + +compares each bit in "flags" to the one in glt->flags only if that bit is active in "mask" +================ +*/ +void TexMgr_FreeTextures (unsigned int flags, unsigned int mask) +{ + gltexture_t *glt, *next; + + for (glt = active_gltextures; glt; glt = next) + { + next = glt->next; + if ((glt->flags & mask) == (flags & mask)) + TexMgr_FreeTexture (glt); + } +} + +/* +================ +TexMgr_FreeTexturesForOwner +================ +*/ +void TexMgr_FreeTexturesForOwner (qmodel_t *owner) +{ + gltexture_t *glt, *next; + + for (glt = active_gltextures; glt; glt = next) + { + next = glt->next; + if (glt && glt->owner == owner) + TexMgr_FreeTexture (glt); + } +} + +/* +================ +TexMgr_DeleteTextureObjects +================ +*/ +void TexMgr_DeleteTextureObjects (void) +{ + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + { + GL_DeleteTexture (glt); + } +} + +/* +================================================================================ + + INIT + +================================================================================ +*/ + +/* +================= +TexMgr_LoadPalette -- johnfitz -- was VID_SetPalette, moved here, renamed, rewritten +================= +*/ +void TexMgr_LoadPalette (void) +{ + byte *pal, *src, *dst; + int i, mark; + FILE *f; + + COM_FOpenFile ("gfx/palette.lmp", &f, NULL); + if (!f) + Sys_Error ("Couldn't load gfx/palette.lmp"); + + mark = Hunk_LowMark (); + pal = (byte *) Hunk_Alloc (768); + if (!fread(pal, 768, 1, f)) + Sys_Error ("Failed reading gfx/palette.lmp"); + fclose(f); + + //standard palette, 255 is transparent + dst = (byte *)d_8to24table; + src = pal; + for (i = 0; i < 256; i++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + ((byte *) &d_8to24table[255]) [3] = 0; + + //fullbright palette, 0-223 are black (for additive blending) + src = pal + 224*3; + dst = (byte *) &d_8to24table_fbright[224]; + for (i = 224; i < 256; i++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + for (i = 0; i < 224; i++) + { + dst = (byte *) &d_8to24table_fbright[i]; + dst[3] = 255; + dst[2] = dst[1] = dst[0] = 0; + } + + //nobright palette, 224-255 are black (for additive blending) + dst = (byte *)d_8to24table_nobright; + src = pal; + for (i = 0; i < 256; i++) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + for (i = 224; i < 256; i++) + { + dst = (byte *) &d_8to24table_nobright[i]; + dst[3] = 255; + dst[2] = dst[1] = dst[0] = 0; + } + + //fullbright palette, for fence textures + memcpy(d_8to24table_fbright_fence, d_8to24table_fbright, 256*4); + d_8to24table_fbright_fence[255] = 0; // Alpha of zero. + + //nobright palette, for fence textures + memcpy(d_8to24table_nobright_fence, d_8to24table_nobright, 256*4); + d_8to24table_nobright_fence[255] = 0; // Alpha of zero. + + //conchars palette, 0 and 255 are transparent + memcpy(d_8to24table_conchars, d_8to24table, 256*4); + ((byte *) &d_8to24table_conchars[0]) [3] = 0; + + Hunk_FreeToLowMark (mark); +} + +/* +================ +TexMgr_NewGame +================ +*/ +void TexMgr_NewGame (void) +{ + TexMgr_FreeTextures (0, TEXPREF_PERSIST); //deletes all textures where TEXPREF_PERSIST is unset + TexMgr_LoadPalette (); +} + +/* +============= +TexMgr_RecalcWarpImageSize -- called during init, and after a vid_restart + +choose safe warpimage size and resize existing warpimage textures +============= +*/ +void TexMgr_RecalcWarpImageSize (void) +{ +// int oldsize = gl_warpimagesize; + int mark; + gltexture_t *glt; + byte *dummy; + + // + // find the new correct size + // + gl_warpimagesize = TexMgr_SafeTextureSize (512); + + while (gl_warpimagesize > vid.width) + gl_warpimagesize >>= 1; + while (gl_warpimagesize > vid.height) + gl_warpimagesize >>= 1; + + // ericw -- removed early exit if (gl_warpimagesize == oldsize). + // after vid_restart TexMgr_ReloadImage reloads textures + // to tx->source_width/source_height, which might not match oldsize. + // fixes: https://sourceforge.net/p/quakespasm/bugs/13/ + + // + // resize the textures in opengl + // + mark = Hunk_LowMark(); + dummy = (byte *) Hunk_Alloc (gl_warpimagesize*gl_warpimagesize*4); + + for (glt = active_gltextures; glt; glt = glt->next) + { + if (glt->flags & TEXPREF_WARPIMAGE) + { + GL_Bind (glt); + glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, gl_warpimagesize, gl_warpimagesize, 0, GL_RGBA, GL_UNSIGNED_BYTE, dummy); + glt->width = glt->height = gl_warpimagesize; + } + } + + Hunk_FreeToLowMark (mark); +} + +/* +================ +TexMgr_Init + +must be called before any texture loading +================ +*/ +void TexMgr_Init (void) +{ + int i; + static byte notexture_data[16] = {159,91,83,255,0,0,0,255,0,0,0,255,159,91,83,255}; //black and pink checker + static byte nulltexture_data[16] = {127,191,255,255,0,0,0,255,0,0,0,255,127,191,255,255}; //black and blue checker + + // init texture list + free_gltextures = (gltexture_t *) Hunk_AllocName (MAX_GLTEXTURES * sizeof(gltexture_t), "gltextures"); + active_gltextures = NULL; + for (i = 0; i < MAX_GLTEXTURES - 1; i++) + free_gltextures[i].next = &free_gltextures[i+1]; + free_gltextures[i].next = NULL; + numgltextures = 0; + + // palette + TexMgr_LoadPalette (); + + Cvar_RegisterVariable (&gl_max_size); + Cvar_RegisterVariable (&gl_picmip); + Cvar_RegisterVariable (&gl_texture_anisotropy); + Cvar_SetCallback (&gl_texture_anisotropy, &TexMgr_Anisotropy_f); + gl_texturemode.string = glmodes[glmode_idx].name; + Cvar_RegisterVariable (&gl_texturemode); + Cvar_SetCallback (&gl_texturemode, &TexMgr_TextureMode_f); + Cmd_AddCommand ("gl_describetexturemodes", &TexMgr_DescribeTextureModes_f); + Cmd_AddCommand ("imagelist", &TexMgr_Imagelist_f); + Cmd_AddCommand ("imagedump", &TexMgr_Imagedump_f); + + // poll max size from hardware + glGetIntegerv (GL_MAX_TEXTURE_SIZE, &gl_hardware_maxsize); + + // load notexture images + notexture = TexMgr_LoadImage (NULL, "notexture", 2, 2, SRC_RGBA, notexture_data, "", (src_offset_t)notexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP); + nulltexture = TexMgr_LoadImage (NULL, "nulltexture", 2, 2, SRC_RGBA, nulltexture_data, "", (src_offset_t)nulltexture_data, TEXPREF_NEAREST | TEXPREF_PERSIST | TEXPREF_NOPICMIP); + + //have to assign these here becuase Mod_Init is called before TexMgr_Init + r_notexture_mip->gltexture = r_notexture_mip2->gltexture = notexture; + + //set safe size for warpimages + gl_warpimagesize = 0; + TexMgr_RecalcWarpImageSize (); +} + +/* +================================================================================ + + IMAGE LOADING + +================================================================================ +*/ + +/* +================ +TexMgr_Pad -- return smallest power of two greater than or equal to s +================ +*/ +int TexMgr_Pad (int s) +{ + int i; + for (i = 1; i < s; i<<=1) + ; + return i; +} + +/* +=============== +TexMgr_SafeTextureSize -- return a size with hardware and user prefs in mind +=============== +*/ +int TexMgr_SafeTextureSize (int s) +{ + int p = (int)gl_max_size.value; + if (!gl_texture_NPOT) + s = TexMgr_Pad(s); + if (p > 0) { + p = TexMgr_Pad(p); + if (p < s) s = p; + } + if (s > gl_hardware_maxsize) + s = gl_hardware_maxsize; + return s; +} + +/* +================ +TexMgr_PadConditional -- only pad if a texture of that size would be padded. (used for tex coords) +================ +*/ +int TexMgr_PadConditional (int s) +{ + if (s < TexMgr_SafeTextureSize(s)) + return TexMgr_Pad(s); + return s; +} + +/* +================ +TexMgr_MipMapW +================ +*/ +static unsigned *TexMgr_MipMapW (unsigned *data, int width, int height) +{ + int i, size; + byte *out, *in; + + out = in = (byte *)data; + size = (width*height)>>1; + + for (i = 0; i < size; i++, out += 4, in += 8) + { + out[0] = (in[0] + in[4])>>1; + out[1] = (in[1] + in[5])>>1; + out[2] = (in[2] + in[6])>>1; + out[3] = (in[3] + in[7])>>1; + } + + return data; +} + +/* +================ +TexMgr_MipMapH +================ +*/ +static unsigned *TexMgr_MipMapH (unsigned *data, int width, int height) +{ + int i, j; + byte *out, *in; + + out = in = (byte *)data; + height>>=1; + width<<=2; + + for (i = 0; i < height; i++, in += width) + { + for (j = 0; j < width; j += 4, out += 4, in += 4) + { + out[0] = (in[0] + in[width+0])>>1; + out[1] = (in[1] + in[width+1])>>1; + out[2] = (in[2] + in[width+2])>>1; + out[3] = (in[3] + in[width+3])>>1; + } + } + + return data; +} + +/* +================ +TexMgr_ResampleTexture -- bilinear resample +================ +*/ +static unsigned *TexMgr_ResampleTexture (unsigned *in, int inwidth, int inheight, qboolean alpha) +{ + byte *nwpx, *nepx, *swpx, *sepx, *dest; + unsigned xfrac, yfrac, x, y, modx, mody, imodx, imody, injump, outjump; + unsigned *out; + int i, j, outwidth, outheight; + + if (inwidth == TexMgr_Pad(inwidth) && inheight == TexMgr_Pad(inheight)) + return in; + + outwidth = TexMgr_Pad(inwidth); + outheight = TexMgr_Pad(inheight); + out = (unsigned *) Hunk_Alloc(outwidth*outheight*4); + + xfrac = ((inwidth-1) << 16) / (outwidth-1); + yfrac = ((inheight-1) << 16) / (outheight-1); + y = outjump = 0; + + for (i = 0; i < outheight; i++) + { + mody = (y>>8) & 0xFF; + imody = 256 - mody; + injump = (y>>16) * inwidth; + x = 0; + + for (j = 0; j < outwidth; j++) + { + modx = (x>>8) & 0xFF; + imodx = 256 - modx; + + nwpx = (byte *)(in + (x>>16) + injump); + nepx = nwpx + 4; + swpx = nwpx + inwidth*4; + sepx = swpx + 4; + + dest = (byte *)(out + outjump + j); + + dest[0] = (nwpx[0]*imodx*imody + nepx[0]*modx*imody + swpx[0]*imodx*mody + sepx[0]*modx*mody)>>16; + dest[1] = (nwpx[1]*imodx*imody + nepx[1]*modx*imody + swpx[1]*imodx*mody + sepx[1]*modx*mody)>>16; + dest[2] = (nwpx[2]*imodx*imody + nepx[2]*modx*imody + swpx[2]*imodx*mody + sepx[2]*modx*mody)>>16; + if (alpha) + dest[3] = (nwpx[3]*imodx*imody + nepx[3]*modx*imody + swpx[3]*imodx*mody + sepx[3]*modx*mody)>>16; + else + dest[3] = 255; + + x += xfrac; + } + outjump += outwidth; + y += yfrac; + } + + return out; +} + +/* +=============== +TexMgr_AlphaEdgeFix + +eliminate pink edges on sprites, etc. +operates in place on 32bit data +=============== +*/ +static void TexMgr_AlphaEdgeFix (byte *data, int width, int height) +{ + int i, j, n = 0, b, c[3] = {0,0,0}, + lastrow, thisrow, nextrow, + lastpix, thispix, nextpix; + byte *dest = data; + + for (i = 0; i < height; i++) + { + lastrow = width * 4 * ((i == 0) ? height-1 : i-1); + thisrow = width * 4 * i; + nextrow = width * 4 * ((i == height-1) ? 0 : i+1); + + for (j = 0; j < width; j++, dest += 4) + { + if (dest[3]) //not transparent + continue; + + lastpix = 4 * ((j == 0) ? width-1 : j-1); + thispix = 4 * j; + nextpix = 4 * ((j == width-1) ? 0 : j+1); + + b = lastrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = thisrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = nextrow + lastpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = lastrow + thispix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = nextrow + thispix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = lastrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = thisrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + b = nextrow + nextpix; if (data[b+3]) {c[0] += data[b]; c[1] += data[b+1]; c[2] += data[b+2]; n++;} + + //average all non-transparent neighbors + if (n) + { + dest[0] = (byte)(c[0]/n); + dest[1] = (byte)(c[1]/n); + dest[2] = (byte)(c[2]/n); + + n = c[0] = c[1] = c[2] = 0; + } + } + } +} + +/* +=============== +TexMgr_PadEdgeFixW -- special case of AlphaEdgeFix for textures that only need it because they were padded + +operates in place on 32bit data, and expects unpadded height and width values +=============== +*/ +static void TexMgr_PadEdgeFixW (byte *data, int width, int height) +{ + byte *src, *dst; + int i, padw, padh; + + padw = TexMgr_PadConditional(width); + padh = TexMgr_PadConditional(height); + + //copy last full column to first empty column, leaving alpha byte at zero + src = data + (width - 1) * 4; + for (i = 0; i < padh; i++) + { + src[4] = src[0]; + src[5] = src[1]; + src[6] = src[2]; + src += padw * 4; + } + + //copy first full column to last empty column, leaving alpha byte at zero + src = data; + dst = data + (padw - 1) * 4; + for (i = 0; i < padh; i++) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + src += padw * 4; + dst += padw * 4; + } +} + +/* +=============== +TexMgr_PadEdgeFixH -- special case of AlphaEdgeFix for textures that only need it because they were padded + +operates in place on 32bit data, and expects unpadded height and width values +=============== +*/ +static void TexMgr_PadEdgeFixH (byte *data, int width, int height) +{ + byte *src, *dst; + int i, padw, padh; + + padw = TexMgr_PadConditional(width); + padh = TexMgr_PadConditional(height); + + //copy last full row to first empty row, leaving alpha byte at zero + dst = data + height * padw * 4; + src = dst - padw * 4; + for (i = 0; i < padw; i++) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + src += 4; + dst += 4; + } + + //copy first full row to last empty row, leaving alpha byte at zero + dst = data + (padh - 1) * padw * 4; + src = data; + for (i = 0; i < padw; i++) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + src += 4; + dst += 4; + } +} + +/* +================ +TexMgr_8to32 +================ +*/ +static unsigned *TexMgr_8to32 (byte *in, int pixels, unsigned int *usepal) +{ + int i; + unsigned *out, *data; + + out = data = (unsigned *) Hunk_Alloc(pixels*4); + + for (i = 0; i < pixels; i++) + *out++ = usepal[*in++]; + + return data; +} + +/* +================ +TexMgr_PadImageW -- return image with width padded up to power-of-two dimentions +================ +*/ +static byte *TexMgr_PadImageW (byte *in, int width, int height, byte padbyte) +{ + int i, j, outwidth; + byte *out, *data; + + if (width == TexMgr_Pad(width)) + return in; + + outwidth = TexMgr_Pad(width); + + out = data = (byte *) Hunk_Alloc(outwidth*height); + + for (i = 0; i < height; i++) + { + for (j = 0; j < width; j++) + *out++ = *in++; + for ( ; j < outwidth; j++) + *out++ = padbyte; + } + + return data; +} + +/* +================ +TexMgr_PadImageH -- return image with height padded up to power-of-two dimentions +================ +*/ +static byte *TexMgr_PadImageH (byte *in, int width, int height, byte padbyte) +{ + int i, srcpix, dstpix; + byte *data, *out; + + if (height == TexMgr_Pad(height)) + return in; + + srcpix = width * height; + dstpix = width * TexMgr_Pad(height); + + out = data = (byte *) Hunk_Alloc(dstpix); + + for (i = 0; i < srcpix; i++) + *out++ = *in++; + for ( ; i < dstpix; i++) + *out++ = padbyte; + + return data; +} + +/* +================ +TexMgr_LoadImage32 -- handles 32bit source data +================ +*/ +static void TexMgr_LoadImage32 (gltexture_t *glt, unsigned *data) +{ + int internalformat, miplevel, mipwidth, mipheight, picmip; + + if (!gl_texture_NPOT) + { + // resample up + data = TexMgr_ResampleTexture (data, glt->width, glt->height, glt->flags & TEXPREF_ALPHA); + glt->width = TexMgr_Pad(glt->width); + glt->height = TexMgr_Pad(glt->height); + } + + // mipmap down + picmip = (glt->flags & TEXPREF_NOPICMIP) ? 0 : q_max((int)gl_picmip.value, 0); + mipwidth = TexMgr_SafeTextureSize (glt->width >> picmip); + mipheight = TexMgr_SafeTextureSize (glt->height >> picmip); + while ((int) glt->width > mipwidth) + { + TexMgr_MipMapW (data, glt->width, glt->height); + glt->width >>= 1; + if (glt->flags & TEXPREF_ALPHA) + TexMgr_AlphaEdgeFix ((byte *)data, glt->width, glt->height); + } + while ((int) glt->height > mipheight) + { + TexMgr_MipMapH (data, glt->width, glt->height); + glt->height >>= 1; + if (glt->flags & TEXPREF_ALPHA) + TexMgr_AlphaEdgeFix ((byte *)data, glt->width, glt->height); + } + + // upload + GL_Bind (glt); + internalformat = (glt->flags & TEXPREF_ALPHA) ? gl_alpha_format : gl_solid_format; + glTexImage2D (GL_TEXTURE_2D, 0, internalformat, glt->width, glt->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + // upload mipmaps + if (glt->flags & TEXPREF_MIPMAP && !(glt->flags & TEXPREF_WARPIMAGE)) // warp image mipmaps are generated later + { + mipwidth = glt->width; + mipheight = glt->height; + + for (miplevel = 1; mipwidth > 1 || mipheight > 1; miplevel++) + { + if (mipwidth > 1) + { + TexMgr_MipMapW (data, mipwidth, mipheight); + mipwidth >>= 1; + } + if (mipheight > 1) + { + TexMgr_MipMapH (data, mipwidth, mipheight); + mipheight >>= 1; + } + glTexImage2D (GL_TEXTURE_2D, miplevel, internalformat, mipwidth, mipheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + } + + // set filter modes + TexMgr_SetFilterModes (glt); +} + +/* +================ +TexMgr_LoadImage8 -- handles 8bit source data, then passes it to LoadImage32 +================ +*/ +static void TexMgr_LoadImage8 (gltexture_t *glt, byte *data) +{ + extern cvar_t gl_fullbrights; + qboolean padw = false, padh = false; + byte padbyte; + unsigned int *usepal; + int i; + + // HACK HACK HACK -- taken from tomazquake + if (strstr(glt->name, "shot1sid") && + glt->width == 32 && glt->height == 32 && + CRC_Block(data, 1024) == 65393) + { + // This texture in b_shell1.bsp has some of the first 32 pixels painted white. + // They are invisible in software, but look really ugly in GL. So we just copy + // 32 pixels from the bottom to make it look nice. + memcpy (data, data + 32*31, 32); + } + + // detect false alpha cases + if (glt->flags & TEXPREF_ALPHA && !(glt->flags & TEXPREF_CONCHARS)) + { + for (i = 0; i < (int) (glt->width * glt->height); i++) + if (data[i] == 255) //transparent index + break; + if (i == (int) (glt->width * glt->height)) + glt->flags -= TEXPREF_ALPHA; + } + + // choose palette and padbyte + if (glt->flags & TEXPREF_FULLBRIGHT) + { + if (glt->flags & TEXPREF_ALPHA) + usepal = d_8to24table_fbright_fence; + else + usepal = d_8to24table_fbright; + padbyte = 0; + } + else if (glt->flags & TEXPREF_NOBRIGHT && gl_fullbrights.value) + { + if (glt->flags & TEXPREF_ALPHA) + usepal = d_8to24table_nobright_fence; + else + usepal = d_8to24table_nobright; + padbyte = 0; + } + else if (glt->flags & TEXPREF_CONCHARS) + { + usepal = d_8to24table_conchars; + padbyte = 0; + } + else + { + usepal = d_8to24table; + padbyte = 255; + } + + // pad each dimention, but only if it's not going to be downsampled later + if (glt->flags & TEXPREF_PAD) + { + if ((int) glt->width < TexMgr_SafeTextureSize(glt->width)) + { + data = TexMgr_PadImageW (data, glt->width, glt->height, padbyte); + glt->width = TexMgr_Pad(glt->width); + padw = true; + } + if ((int) glt->height < TexMgr_SafeTextureSize(glt->height)) + { + data = TexMgr_PadImageH (data, glt->width, glt->height, padbyte); + glt->height = TexMgr_Pad(glt->height); + padh = true; + } + } + + // convert to 32bit + data = (byte *)TexMgr_8to32(data, glt->width * glt->height, usepal); + + // fix edges + if (glt->flags & TEXPREF_ALPHA) + TexMgr_AlphaEdgeFix (data, glt->width, glt->height); + else + { + if (padw) + TexMgr_PadEdgeFixW (data, glt->source_width, glt->source_height); + if (padh) + TexMgr_PadEdgeFixH (data, glt->source_width, glt->source_height); + } + + // upload it + TexMgr_LoadImage32 (glt, (unsigned *)data); +} + +/* +================ +TexMgr_LoadLightmap -- handles lightmap data +================ +*/ +static void TexMgr_LoadLightmap (gltexture_t *glt, byte *data) +{ + const qboolean wide10bits = !!r_lightmapwide.value; + const GLenum type = wide10bits ? GL_UNSIGNED_INT_10_10_10_2 : GL_UNSIGNED_BYTE; + const GLint internalfmt = wide10bits ? GL_RGB10_A2 : lightmap_bytes; + + // upload it + GL_Bind (glt); + glTexImage2D (GL_TEXTURE_2D, 0, internalfmt, glt->width, glt->height, 0, gl_lightmap_format, type, data); + + // set filter modes + TexMgr_SetFilterModes (glt); +} + +/* +================ +TexMgr_LoadImage -- the one entry point for loading all textures +================ +*/ +gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int height, enum srcformat format, + byte *data, const char *source_file, src_offset_t source_offset, unsigned flags) +{ + unsigned short crc; + gltexture_t *glt; + int mark; + + if (isDedicated) + return NULL; + + // cache check + switch (format) + { + case SRC_INDEXED: + crc = CRC_Block(data, width * height); + break; + case SRC_LIGHTMAP: + crc = CRC_Block(data, width * height * lightmap_bytes); + break; + case SRC_RGBA: + crc = CRC_Block(data, width * height * 4); + break; + default: /* not reachable but avoids compiler warnings */ + crc = 0; + } + if ((flags & TEXPREF_OVERWRITE) && (glt = TexMgr_FindTexture (owner, name))) + { + if (glt->source_crc == crc) + return glt; + } + else + glt = TexMgr_NewTexture (); + + // copy data + glt->owner = owner; + q_strlcpy (glt->name, name, sizeof(glt->name)); + glt->width = width; + glt->height = height; + glt->flags = flags; + glt->shirt = -1; + glt->pants = -1; + q_strlcpy (glt->source_file, source_file, sizeof(glt->source_file)); + glt->source_offset = source_offset; + glt->source_format = format; + glt->source_width = width; + glt->source_height = height; + glt->source_crc = crc; + + //upload it + mark = Hunk_LowMark(); + + switch (glt->source_format) + { + case SRC_INDEXED: + TexMgr_LoadImage8 (glt, data); + break; + case SRC_LIGHTMAP: + TexMgr_LoadLightmap (glt, data); + break; + case SRC_RGBA: + TexMgr_LoadImage32 (glt, (unsigned *)data); + break; + } + + Hunk_FreeToLowMark(mark); + + return glt; +} + +/* +================================================================================ + + COLORMAPPING AND TEXTURE RELOADING + +================================================================================ +*/ + +/* +================ +TexMgr_ReloadImage -- reloads a texture, and colormaps it if needed +================ +*/ +void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants) +{ + byte translation[256]; + byte *src, *dst, *data = NULL, *translated; + int mark, size, i; + +// +// get source data +// + mark = Hunk_LowMark (); + + if (glt->source_file[0] && glt->source_offset) { + //lump inside file + FILE *f; + int sz; + COM_FOpenFile(glt->source_file, &f, NULL); + if (!f) goto invalid; + fseek (f, glt->source_offset, SEEK_CUR); + size = glt->source_width * glt->source_height; + /* should be SRC_INDEXED, but no harm being paranoid: */ + if (glt->source_format == SRC_RGBA) { + size *= 4; + } + else if (glt->source_format == SRC_LIGHTMAP) { + size *= lightmap_bytes; + } + data = (byte *) Hunk_Alloc (size); + sz = (int) fread (data, 1, size, f); + fclose (f); + if (sz != size) { + Hunk_FreeToLowMark(mark); + Host_Error("Read error for %s", glt->name); + } + } + else if (glt->source_file[0] && !glt->source_offset) { + data = Image_LoadImage (glt->source_file, (int *)&glt->source_width, (int *)&glt->source_height); //simple file + } + else if (!glt->source_file[0] && glt->source_offset) { + data = (byte *) glt->source_offset; //image in memory + } + if (!data) { +invalid: Con_Printf ("TexMgr_ReloadImage: invalid source for %s\n", glt->name); + Hunk_FreeToLowMark(mark); + return; + } + + glt->width = glt->source_width; + glt->height = glt->source_height; +// +// apply shirt and pants colors +// +// if shirt and pants are -1,-1, use existing shirt and pants colors +// if existing shirt and pants colors are -1,-1, don't bother colormapping + if (shirt > -1 && pants > -1) + { + if (glt->source_format == SRC_INDEXED) + { + glt->shirt = shirt; + glt->pants = pants; + } + else + Con_Printf ("TexMgr_ReloadImage: can't colormap a non SRC_INDEXED texture: %s\n", glt->name); + } + if (glt->shirt > -1 && glt->pants > -1) + { + //create new translation table + for (i = 0; i < 256; i++) + translation[i] = i; + + shirt = glt->shirt * 16; + if (shirt < 128) + { + for (i = 0; i < 16; i++) + translation[TOP_RANGE+i] = shirt + i; + } + else + { + for (i = 0; i < 16; i++) + translation[TOP_RANGE+i] = shirt+15-i; + } + + pants = glt->pants * 16; + if (pants < 128) + { + for (i = 0; i < 16; i++) + translation[BOTTOM_RANGE+i] = pants + i; + } + else + { + for (i = 0; i < 16; i++) + translation[BOTTOM_RANGE+i] = pants+15-i; + } + + //translate texture + size = glt->width * glt->height; + dst = translated = (byte *) Hunk_Alloc (size); + src = data; + + for (i = 0; i < size; i++) + *dst++ = translation[*src++]; + + data = translated; + } +// +// upload it +// + switch (glt->source_format) + { + case SRC_INDEXED: + TexMgr_LoadImage8 (glt, data); + break; + case SRC_LIGHTMAP: + TexMgr_LoadLightmap (glt, data); + break; + case SRC_RGBA: + TexMgr_LoadImage32 (glt, (unsigned *)data); + break; + } + + Hunk_FreeToLowMark(mark); +} + +/* +================ +TexMgr_ReloadImages -- reloads all texture images. called only by vid_restart +================ +*/ +void TexMgr_ReloadImages (void) +{ + gltexture_t *glt; + +// ericw -- tricky bug: if the hunk is almost full, an allocation in TexMgr_ReloadImage +// triggers cache items to be freed, which calls back into TexMgr to free the +// texture. If this frees 'glt' in the loop below, the active_gltextures +// list gets corrupted. +// A test case is jam3_tronyn.bsp with -heapsize 65536, and do several mode +// switches/fullscreen toggles +// 2015-09-04 -- Cache_Flush workaround was causing issues (http://sourceforge.net/p/quakespasm/bugs/10/) +// switching to a boolean flag. + in_reload_images = true; + + for (glt = active_gltextures; glt; glt = glt->next) + { + glGenTextures(1, &glt->texnum); + TexMgr_ReloadImage (glt, -1, -1); + } + + in_reload_images = false; +} + +/* +================ +TexMgr_ReloadNobrightImages -- reloads all texture that were loaded with the nobright palette. called when gl_fullbrights changes +================ +*/ +void TexMgr_ReloadNobrightImages (void) +{ + gltexture_t *glt; + + for (glt = active_gltextures; glt; glt = glt->next) + if (glt->flags & TEXPREF_NOBRIGHT) + TexMgr_ReloadImage(glt, -1, -1); +} + +/* +================================================================================ + + TEXTURE BINDING / TEXTURE UNIT SWITCHING + +================================================================================ +*/ + +static GLuint currenttexture[3] = {GL_UNUSED_TEXTURE, GL_UNUSED_TEXTURE, GL_UNUSED_TEXTURE}; // to avoid unnecessary texture sets +static GLenum currenttarget = GL_TEXTURE0_ARB; +qboolean mtexenabled = false; + +/* +================ +GL_SelectTexture -- johnfitz -- rewritten +================ +*/ +void GL_SelectTexture (GLenum target) +{ + if (target == currenttarget) + return; + + GL_SelectTextureFunc(target); + currenttarget = target; +} + +/* +================ +GL_DisableMultitexture -- selects texture unit 0 +================ +*/ +void GL_DisableMultitexture(void) +{ + if (mtexenabled) + { + glDisable(GL_TEXTURE_2D); + GL_SelectTexture(GL_TEXTURE0_ARB); + mtexenabled = false; + } +} + +/* +================ +GL_EnableMultitexture -- selects texture unit 1 +================ +*/ +void GL_EnableMultitexture(void) +{ + if (gl_mtexable) + { + GL_SelectTexture(GL_TEXTURE1_ARB); + glEnable(GL_TEXTURE_2D); + mtexenabled = true; + } +} + +/* +================ +GL_Bind -- johnfitz -- heavy revision +================ +*/ +void GL_Bind (gltexture_t *texture) +{ + if (!texture) + texture = nulltexture; + + if (texture->texnum != currenttexture[currenttarget - GL_TEXTURE0_ARB]) + { + currenttexture[currenttarget - GL_TEXTURE0_ARB] = texture->texnum; + glBindTexture (GL_TEXTURE_2D, texture->texnum); + texture->visframe = r_framecount; + } +} + +/* +================ +GL_DeleteTexture -- ericw + +Wrapper around glDeleteTextures that also clears the given texture number +from our per-TMU cached texture binding table. +================ +*/ +static void GL_DeleteTexture (gltexture_t *texture) +{ + glDeleteTextures (1, &texture->texnum); + + if (texture->texnum == currenttexture[0]) currenttexture[0] = GL_UNUSED_TEXTURE; + if (texture->texnum == currenttexture[1]) currenttexture[1] = GL_UNUSED_TEXTURE; + if (texture->texnum == currenttexture[2]) currenttexture[2] = GL_UNUSED_TEXTURE; + + texture->texnum = 0; +} + +/* +================ +GL_ClearBindings -- ericw + +Invalidates cached bindings, so the next GL_Bind calls for each TMU will +make real glBindTexture calls. +Call this after changing the binding outside of GL_Bind. +================ +*/ +void GL_ClearBindings(void) +{ + int i; + for (i = 0; i < 3; i++) + { + currenttexture[i] = GL_UNUSED_TEXTURE; + } +} diff --git a/Quake/gl_texmgr.hpp b/Quake/gl_texmgr.hpp new file mode 100644 index 0000000..91d0562 --- /dev/null +++ b/Quake/gl_texmgr.hpp @@ -0,0 +1,108 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _GL_TEXMAN_H +#define _GL_TEXMAN_H + +//gl_texmgr.h -- fitzquake's texture manager. manages opengl texture images + +#define TEXPREF_NONE 0x0000 +#define TEXPREF_MIPMAP 0x0001 // generate mipmaps +// TEXPREF_NEAREST and TEXPREF_LINEAR aren't supposed to be ORed with TEX_MIPMAP +#define TEXPREF_LINEAR 0x0002 // force linear +#define TEXPREF_NEAREST 0x0004 // force nearest +#define TEXPREF_ALPHA 0x0008 // allow alpha +#define TEXPREF_PAD 0x0010 // allow padding +#define TEXPREF_PERSIST 0x0020 // never free +#define TEXPREF_OVERWRITE 0x0040 // overwrite existing same-name texture +#define TEXPREF_NOPICMIP 0x0080 // always load full-sized +#define TEXPREF_FULLBRIGHT 0x0100 // use fullbright mask palette +#define TEXPREF_NOBRIGHT 0x0200 // use nobright mask palette +#define TEXPREF_CONCHARS 0x0400 // use conchars palette +#define TEXPREF_WARPIMAGE 0x0800 // resize this texture when warpimagesize changes + +enum srcformat {SRC_INDEXED, SRC_LIGHTMAP, SRC_RGBA}; + +typedef uintptr_t src_offset_t; + +typedef struct gltexture_s { +//managed by texture manager + GLuint texnum; + struct gltexture_s *next; + qmodel_t *owner; +//managed by image loading + char name[64]; + unsigned int width; //size of image as it exists in opengl + unsigned int height; //size of image as it exists in opengl + unsigned int flags; + char source_file[MAX_QPATH]; //relative filepath to data source, or "" if source is in memory + src_offset_t source_offset; //byte offset into file, or memory address + enum srcformat source_format; //format of pixel data (indexed, lightmap, or rgba) + unsigned int source_width; //size of image in source data + unsigned int source_height; //size of image in source data + unsigned short source_crc; //generated by source data before modifications + signed char shirt; //0-13 shirt color, or -1 if never colormapped + signed char pants; //0-13 pants color, or -1 if never colormapped +//used for rendering + int visframe; //matches r_framecount if texture was bound this frame +} gltexture_t; + +extern gltexture_t *notexture; +extern gltexture_t *nulltexture; + +extern unsigned int d_8to24table[256]; +extern unsigned int d_8to24table_fbright[256]; +extern unsigned int d_8to24table_nobright[256]; +extern unsigned int d_8to24table_conchars[256]; + +// TEXTURE MANAGER + +float TexMgr_FrameUsage (void); +gltexture_t *TexMgr_FindTexture (qmodel_t *owner, const char *name); +gltexture_t *TexMgr_NewTexture (void); +void TexMgr_FreeTexture (gltexture_t *kill); +void TexMgr_FreeTextures (unsigned int flags, unsigned int mask); +void TexMgr_FreeTexturesForOwner (qmodel_t *owner); +void TexMgr_NewGame (void); +void TexMgr_Init (void); +void TexMgr_DeleteTextureObjects (void); + +// IMAGE LOADING +gltexture_t *TexMgr_LoadImage (qmodel_t *owner, const char *name, int width, int height, enum srcformat format, + byte *data, const char *source_file, src_offset_t source_offset, unsigned flags); +void TexMgr_ReloadImage (gltexture_t *glt, int shirt, int pants); +void TexMgr_ReloadImages (void); +void TexMgr_ReloadNobrightImages (void); + +int TexMgr_Pad(int s); +int TexMgr_SafeTextureSize (int s); +int TexMgr_PadConditional (int s); + +// TEXTURE BINDING & TEXTURE UNIT SWITCHING + +void GL_SelectTexture (GLenum target); +void GL_DisableMultitexture (void); //selects texture unit 0 +void GL_EnableMultitexture (void); //selects texture unit 1 +void GL_Bind (gltexture_t *texture); +void GL_ClearBindings (void); + +#endif /* _GL_TEXMAN_H */ + diff --git a/Quake/gl_vidsdl.cpp b/Quake/gl_vidsdl.cpp new file mode 100644 index 0000000..6218d93 --- /dev/null +++ b/Quake/gl_vidsdl.cpp @@ -0,0 +1,2392 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// gl_vidsdl.c -- SDL GL vid component + +#include "quakedef.hpp" +#include "cfgfile.hpp" +#include "bgmusic.hpp" +#include "resource.hpp" +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + +//ericw -- for putting the driver into multithreaded mode +#ifdef __APPLE__ +#include +#endif + +#define MAX_MODE_LIST 600 //johnfitz -- was 30 +#define MAX_BPPS_LIST 5 +#define MAX_RATES_LIST 20 +#define WARP_WIDTH 320 +#define WARP_HEIGHT 200 +#define MAXWIDTH 10000 +#define MAXHEIGHT 10000 + +#define DEFAULT_SDL_FLAGS SDL_OPENGL + +#define DEFAULT_REFRESHRATE 60 + +typedef struct { + int width; + int height; + int refreshrate; + int bpp; +} vmode_t; + +static const char *gl_vendor; +static const char *gl_renderer; +static const char *gl_version; +static int gl_version_major; +static int gl_version_minor; +static const char *gl_extensions; +static char * gl_extensions_nice; + +static vmode_t modelist[MAX_MODE_LIST]; +static int nummodes; + +static qboolean vid_initialized = false; + +#if defined(USE_SDL2) +static SDL_Window *draw_context; +static SDL_GLContext gl_context; +#else +static SDL_Surface *draw_context; +#endif + +static qboolean vid_locked = false; //johnfitz +static qboolean vid_changed = false; + +static void VID_Menu_Init (void); //johnfitz +static void VID_Menu_f (void); //johnfitz +static void VID_MenuDraw (void); +static void VID_MenuKey (int key); + +static void ClearAllStates (void); +static void GL_Init (void); +static void GL_SetupState (void); //johnfitz + +viddef_t vid; // global video state +modestate_t modestate = MS_UNINIT; +qboolean scr_skipupdate; + +qboolean gl_mtexable = false; +qboolean gl_packed_pixels = false; +qboolean gl_texture_env_combine = false; //johnfitz +qboolean gl_texture_env_add = false; //johnfitz +qboolean gl_swap_control = false; //johnfitz +qboolean gl_anisotropy_able = false; //johnfitz +float gl_max_anisotropy; //johnfitz +qboolean gl_texture_NPOT = false; //ericw +qboolean gl_vbo_able = false; //ericw +qboolean gl_glsl_able = false; //ericw +GLint gl_max_texture_units = 0; //ericw +qboolean gl_glsl_gamma_able = false; //ericw +qboolean gl_glsl_alias_able = false; //ericw +int gl_stencilbits; + +PFNGLMULTITEXCOORD2FARBPROC GL_MTexCoord2fFunc = NULL; //johnfitz +PFNGLACTIVETEXTUREARBPROC GL_SelectTextureFunc = NULL; //johnfitz +PFNGLCLIENTACTIVETEXTUREARBPROC GL_ClientActiveTextureFunc = NULL; //ericw +PFNGLBINDBUFFERARBPROC GL_BindBufferFunc = NULL; //ericw +PFNGLBUFFERDATAARBPROC GL_BufferDataFunc = NULL; //ericw +PFNGLBUFFERSUBDATAARBPROC GL_BufferSubDataFunc = NULL; //ericw +PFNGLDELETEBUFFERSARBPROC GL_DeleteBuffersFunc = NULL; //ericw +PFNGLGENBUFFERSARBPROC GL_GenBuffersFunc = NULL; //ericw + +QS_PFNGLCREATESHADERPROC GL_CreateShaderFunc = NULL; //ericw +QS_PFNGLDELETESHADERPROC GL_DeleteShaderFunc = NULL; //ericw +QS_PFNGLDELETEPROGRAMPROC GL_DeleteProgramFunc = NULL; //ericw +QS_PFNGLSHADERSOURCEPROC GL_ShaderSourceFunc = NULL; //ericw +QS_PFNGLCOMPILESHADERPROC GL_CompileShaderFunc = NULL; //ericw +QS_PFNGLGETSHADERIVPROC GL_GetShaderivFunc = NULL; //ericw +QS_PFNGLGETSHADERINFOLOGPROC GL_GetShaderInfoLogFunc = NULL; //ericw +QS_PFNGLGETPROGRAMIVPROC GL_GetProgramivFunc = NULL; //ericw +QS_PFNGLGETPROGRAMINFOLOGPROC GL_GetProgramInfoLogFunc = NULL; //ericw +QS_PFNGLCREATEPROGRAMPROC GL_CreateProgramFunc = NULL; //ericw +QS_PFNGLATTACHSHADERPROC GL_AttachShaderFunc = NULL; //ericw +QS_PFNGLLINKPROGRAMPROC GL_LinkProgramFunc = NULL; //ericw +QS_PFNGLBINDATTRIBLOCATIONFUNC GL_BindAttribLocationFunc = NULL; //ericw +QS_PFNGLUSEPROGRAMPROC GL_UseProgramFunc = NULL; //ericw +QS_PFNGLGETATTRIBLOCATIONPROC GL_GetAttribLocationFunc = NULL; //ericw +QS_PFNGLVERTEXATTRIBPOINTERPROC GL_VertexAttribPointerFunc = NULL; //ericw +QS_PFNGLENABLEVERTEXATTRIBARRAYPROC GL_EnableVertexAttribArrayFunc = NULL; //ericw +QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC GL_DisableVertexAttribArrayFunc = NULL; //ericw +QS_PFNGLGETUNIFORMLOCATIONPROC GL_GetUniformLocationFunc = NULL; //ericw +QS_PFNGLUNIFORM1IPROC GL_Uniform1iFunc = NULL; //ericw +QS_PFNGLUNIFORM1FPROC GL_Uniform1fFunc = NULL; //ericw +QS_PFNGLUNIFORM3FPROC GL_Uniform3fFunc = NULL; //ericw +QS_PFNGLUNIFORM4FPROC GL_Uniform4fFunc = NULL; //ericw + +QS_PFNGENERATEMIPMAP GL_GenerateMipmap = NULL; + +//==================================== + +//johnfitz -- new cvars +static cvar_t vid_fullscreen = {"vid_fullscreen", "0", CVAR_ARCHIVE}; // QuakeSpasm, was "1" +static cvar_t vid_width = {"vid_width", "800", CVAR_ARCHIVE}; // QuakeSpasm, was 640 +static cvar_t vid_height = {"vid_height", "600", CVAR_ARCHIVE}; // QuakeSpasm, was 480 +static cvar_t vid_bpp = {"vid_bpp", "16", CVAR_ARCHIVE}; +static cvar_t vid_refreshrate = {"vid_refreshrate", "60", CVAR_ARCHIVE}; +static cvar_t vid_vsync = {"vid_vsync", "0", CVAR_ARCHIVE}; +static cvar_t vid_fsaa = {"vid_fsaa", "0", CVAR_ARCHIVE}; // QuakeSpasm +static cvar_t vid_desktopfullscreen = {"vid_desktopfullscreen", "0", CVAR_ARCHIVE}; // QuakeSpasm +static cvar_t vid_borderless = {"vid_borderless", "0", CVAR_ARCHIVE}; // QuakeSpasm +//johnfitz + +cvar_t vid_gamma = {"gamma", "1", CVAR_ARCHIVE}; //johnfitz -- moved here from view.c +cvar_t vid_contrast = {"contrast", "1", CVAR_ARCHIVE}; //QuakeSpasm, MarkV + +//========================================================================== +// +// HARDWARE GAMMA -- johnfitz +// +//========================================================================== + +#define USE_GAMMA_RAMPS 0 + +#if USE_GAMMA_RAMPS +static unsigned short vid_gamma_red[256]; +static unsigned short vid_gamma_green[256]; +static unsigned short vid_gamma_blue[256]; + +static unsigned short vid_sysgamma_red[256]; +static unsigned short vid_sysgamma_green[256]; +static unsigned short vid_sysgamma_blue[256]; +#endif + +static qboolean gammaworks = false; // whether hw-gamma works +static int fsaa; + +/* +================ +VID_Gamma_SetGamma -- apply gamma correction +================ +*/ +static void VID_Gamma_SetGamma (void) +{ + if (gl_glsl_gamma_able) + return; + + if (draw_context && gammaworks) + { + float value; + + if (vid_gamma.value > (1.0f / GAMMA_MAX)) + value = 1.0f / vid_gamma.value; + else + value = GAMMA_MAX; + +#if defined(USE_SDL2) +# if USE_GAMMA_RAMPS + if (SDL_SetWindowGammaRamp(draw_context, vid_gamma_red, vid_gamma_green, vid_gamma_blue) != 0) + Con_Printf ("VID_Gamma_SetGamma: failed on SDL_SetWindowGammaRamp\n"); +# else + if (SDL_SetWindowBrightness(draw_context, value) != 0) + Con_Printf ("VID_Gamma_SetGamma: failed on SDL_SetWindowBrightness\n"); +# endif +#else /* USE_SDL2 */ +# if USE_GAMMA_RAMPS + if (SDL_SetGammaRamp(vid_gamma_red, vid_gamma_green, vid_gamma_blue) == -1) + Con_Printf ("VID_Gamma_SetGamma: failed on SDL_SetGammaRamp\n"); +# else + if (SDL_SetGamma(value,value,value) == -1) + Con_Printf ("VID_Gamma_SetGamma: failed on SDL_SetGamma\n"); +# endif +#endif /* USE_SDL2 */ + } +} + +/* +================ +VID_Gamma_Restore -- restore system gamma +================ +*/ +static void VID_Gamma_Restore (void) +{ + if (gl_glsl_gamma_able) + return; + + if (draw_context && gammaworks) + { +#if defined(USE_SDL2) +# if USE_GAMMA_RAMPS + if (SDL_SetWindowGammaRamp(draw_context, vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) != 0) + Con_Printf ("VID_Gamma_Restore: failed on SDL_SetWindowGammaRamp\n"); +# else + if (SDL_SetWindowBrightness(draw_context, 1) != 0) + Con_Printf ("VID_Gamma_Restore: failed on SDL_SetWindowBrightness\n"); +# endif +#else /* USE_SDL2 */ +# if USE_GAMMA_RAMPS + if (SDL_SetGammaRamp(vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == -1) + Con_Printf ("VID_Gamma_Restore: failed on SDL_SetGammaRamp\n"); +# else + if (SDL_SetGamma(1, 1, 1) == -1) + Con_Printf ("VID_Gamma_Restore: failed on SDL_SetGamma\n"); +# endif +#endif /* USE_SDL2 */ + } +} + +/* +================ +VID_Gamma_Shutdown -- called on exit +================ +*/ +static void VID_Gamma_Shutdown (void) +{ + VID_Gamma_Restore (); +} + +/* +================ +VID_Gamma_f -- callback when the cvar changes +================ +*/ +static void VID_Gamma_f (cvar_t *var) +{ + if (gl_glsl_gamma_able) + return; + +#if USE_GAMMA_RAMPS + int i; + + for (i = 0; i < 256; i++) + { + vid_gamma_red[i] = + CLAMP(0, (int) ((255 * pow((i + 0.5)/255.5, vid_gamma.value) + 0.5) * vid_contrast.value), 255) << 8; + vid_gamma_green[i] = vid_gamma_red[i]; + vid_gamma_blue[i] = vid_gamma_red[i]; + } +#endif + VID_Gamma_SetGamma (); +} + +/* +================ +VID_Gamma_Init -- call on init +================ +*/ +static void VID_Gamma_Init (void) +{ + Cvar_RegisterVariable (&vid_gamma); + Cvar_RegisterVariable (&vid_contrast); + Cvar_SetCallback (&vid_gamma, VID_Gamma_f); + Cvar_SetCallback (&vid_contrast, VID_Gamma_f); + + if (gl_glsl_gamma_able) + return; + +#if defined(USE_SDL2) +# if USE_GAMMA_RAMPS + gammaworks = (SDL_GetWindowGammaRamp(draw_context, vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == 0); + if (gammaworks) + gammaworks = (SDL_SetWindowGammaRamp(draw_context, vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == 0); +# else + gammaworks = (SDL_SetWindowBrightness(draw_context, 1) == 0); +# endif +#else /* USE_SDL2 */ +# if USE_GAMMA_RAMPS + gammaworks = (SDL_GetGammaRamp(vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == 0); + if (gammaworks) + gammaworks = (SDL_SetGammaRamp(vid_sysgamma_red, vid_sysgamma_green, vid_sysgamma_blue) == 0); +# else + gammaworks = (SDL_SetGamma(1, 1, 1) == 0); +# endif +#endif /* USE_SDL2 */ + + if (!gammaworks) + Con_SafePrintf("gamma adjustment not available\n"); +} + +/* +====================== +VID_GetCurrentWidth +====================== +*/ +static int VID_GetCurrentWidth (void) +{ +#if defined(USE_SDL2) + int w = 0, h = 0; + SDL_GetWindowSize(draw_context, &w, &h); + return w; +#else + return draw_context->w; +#endif +} + +/* +======================= +VID_GetCurrentHeight +======================= +*/ +static int VID_GetCurrentHeight (void) +{ +#if defined(USE_SDL2) + int w = 0, h = 0; + SDL_GetWindowSize(draw_context, &w, &h); + return h; +#else + return draw_context->h; +#endif +} + +/* +==================== +VID_GetCurrentRefreshRate +==================== +*/ +static int VID_GetCurrentRefreshRate (void) +{ +#if defined(USE_SDL2) + SDL_DisplayMode mode; + int current_display; + + current_display = SDL_GetWindowDisplayIndex(draw_context); + + if (0 != SDL_GetCurrentDisplayMode(current_display, &mode)) + return DEFAULT_REFRESHRATE; + + return mode.refresh_rate; +#else + // SDL1.2 doesn't support refresh rates + return DEFAULT_REFRESHRATE; +#endif +} + + +/* +==================== +VID_GetCurrentBPP +==================== +*/ +static int VID_GetCurrentBPP (void) +{ +#if defined(USE_SDL2) + const Uint32 pixelFormat = SDL_GetWindowPixelFormat(draw_context); + return SDL_BITSPERPIXEL(pixelFormat); +#else + return draw_context->format->BitsPerPixel; +#endif +} + +/* +==================== +VID_GetFullscreen + +returns true if we are in regular fullscreen or "desktop fullscren" +==================== +*/ +static qboolean VID_GetFullscreen (void) +{ +#if defined(USE_SDL2) + return (SDL_GetWindowFlags(draw_context) & SDL_WINDOW_FULLSCREEN) != 0; +#else + return (draw_context->flags & SDL_FULLSCREEN) != 0; +#endif +} + +/* +==================== +VID_GetDesktopFullscreen + +returns true if we are specifically in "desktop fullscreen" mode +==================== +*/ +static qboolean VID_GetDesktopFullscreen (void) +{ +#if defined(USE_SDL2) + return (SDL_GetWindowFlags(draw_context) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP; +#else + return false; +#endif +} + +/* +==================== +VID_GetVSync +==================== +*/ +static qboolean VID_GetVSync (void) +{ +#if defined(USE_SDL2) + return SDL_GL_GetSwapInterval() == 1; +#else + int swap_control; + if (SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &swap_control) == 0) + return swap_control > 0; + return false; +#endif +} + +/* +==================== +VID_GetWindow + +used by pl_win.c +==================== +*/ +void *VID_GetWindow (void) +{ +#if defined(USE_SDL2) + return draw_context; +#else + return NULL; +#endif +} + +/* +==================== +VID_HasMouseOrInputFocus +==================== +*/ +qboolean VID_HasMouseOrInputFocus (void) +{ +#if defined(USE_SDL2) + return (SDL_GetWindowFlags(draw_context) & (SDL_WINDOW_MOUSE_FOCUS | SDL_WINDOW_INPUT_FOCUS)) != 0; +#else + return (SDL_GetAppState() & (SDL_APPMOUSEFOCUS | SDL_APPINPUTFOCUS)) != 0; +#endif +} + +/* +==================== +VID_IsMinimized +==================== +*/ +qboolean VID_IsMinimized (void) +{ +#if defined(USE_SDL2) + return !(SDL_GetWindowFlags(draw_context) & SDL_WINDOW_SHOWN); +#else + /* SDL_APPACTIVE in SDL 1.x means "not minimized" */ + return !(SDL_GetAppState() & SDL_APPACTIVE); +#endif +} + +#if defined(USE_SDL2) +/* +================ +VID_SDL2_GetDisplayMode + +Returns a pointer to a statically allocated SDL_DisplayMode structure +if there is one with the requested params on the default display. +Otherwise returns NULL. + +This is passed to SDL_SetWindowDisplayMode to specify a pixel format +with the requested bpp. If we didn't care about bpp we could just pass NULL. +================ +*/ +static SDL_DisplayMode *VID_SDL2_GetDisplayMode(int width, int height, int refreshrate, int bpp) +{ + static SDL_DisplayMode mode; + const int sdlmodes = SDL_GetNumDisplayModes(0); + int i; + + for (i = 0; i < sdlmodes; i++) + { + if (SDL_GetDisplayMode(0, i, &mode) != 0) + continue; + + if (mode.w == width && mode.h == height + && SDL_BITSPERPIXEL(mode.format) == bpp + && mode.refresh_rate == refreshrate) + { + return &mode; + } + } + return NULL; +} +#endif /* USE_SDL2 */ + +/* +================ +VID_ValidMode +================ +*/ +static qboolean VID_ValidMode (int width, int height, int refreshrate, int bpp, qboolean fullscreen) +{ +// ignore width / height / bpp if vid_desktopfullscreen is enabled + if (fullscreen && vid_desktopfullscreen.value) + return true; + + if (width < 320) + return false; + + if (height < 200) + return false; + +#if defined(USE_SDL2) + if (fullscreen && VID_SDL2_GetDisplayMode(width, height, refreshrate, bpp) == NULL) + bpp = 0; +#else + { + Uint32 flags = DEFAULT_SDL_FLAGS; + if (fullscreen) + flags |= SDL_FULLSCREEN; + + bpp = SDL_VideoModeOK(width, height, bpp, flags); + } +#endif + + switch (bpp) + { + case 16: + case 24: + case 32: + break; + default: + return false; + } + + return true; +} + +/* +================ +VID_SetMode +================ +*/ +static qboolean VID_SetMode (int width, int height, int refreshrate, int bpp, qboolean fullscreen) +{ + int temp; + Uint32 flags; + char caption[50]; + int depthbits, stencilbits; + int fsaa_obtained; +#if defined(USE_SDL2) + int previous_display; +#endif + + // so Con_Printfs don't mess us up by forcing vid and snd updates + temp = scr_disabled_for_loading; + scr_disabled_for_loading = true; + + CDAudio_Pause (); + BGM_Pause (); + + /* z-buffer depth */ + if (bpp == 16) + { + depthbits = 16; + stencilbits = 0; + } + else + { + depthbits = 24; + stencilbits = 8; + } + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthbits); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilbits); + + /* fsaa */ + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, fsaa > 0 ? 1 : 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fsaa); + + q_snprintf(caption, sizeof(caption), "QuakeSpasm " QUAKESPASM_VER_STRING); + +#if defined(USE_SDL2) + /* Create the window if needed, hidden */ + if (!draw_context) + { + flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN; + + if (vid_borderless.value) + flags |= SDL_WINDOW_BORDERLESS; + + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + if (!draw_context) { // scale back fsaa + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + } + if (!draw_context) { // scale back SDL_GL_DEPTH_SIZE + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + } + if (!draw_context) { // scale back SDL_GL_STENCIL_SIZE + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); + draw_context = SDL_CreateWindow (caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); + } + if (!draw_context) + Sys_Error ("Couldn't create window"); + + previous_display = -1; + } + else + { + previous_display = SDL_GetWindowDisplayIndex(draw_context); + } + + /* Ensure the window is not fullscreen */ + if (VID_GetFullscreen ()) + { + if (SDL_SetWindowFullscreen (draw_context, 0) != 0) + Sys_Error("Couldn't set fullscreen state mode"); + } + + /* Set window size and display mode */ + SDL_SetWindowSize (draw_context, width, height); + if (previous_display >= 0) + SDL_SetWindowPosition (draw_context, SDL_WINDOWPOS_CENTERED_DISPLAY(previous_display), SDL_WINDOWPOS_CENTERED_DISPLAY(previous_display)); + else + SDL_SetWindowPosition(draw_context, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_SetWindowDisplayMode (draw_context, VID_SDL2_GetDisplayMode(width, height, refreshrate, bpp)); + SDL_SetWindowBordered (draw_context, vid_borderless.value ? SDL_FALSE : SDL_TRUE); + + /* Make window fullscreen if needed, and show the window */ + + if (fullscreen) { + const Uint32 flag = vid_desktopfullscreen.value ? + SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; + if (SDL_SetWindowFullscreen (draw_context, flag) != 0) + Sys_Error ("Couldn't set fullscreen state mode"); + } + + SDL_ShowWindow (draw_context); + SDL_RaiseWindow (draw_context); + + /* Create GL context if needed */ + if (!gl_context) { + gl_context = SDL_GL_CreateContext(draw_context); + if (!gl_context) + Sys_Error("Couldn't create GL context"); + } + + gl_swap_control = true; + if (SDL_GL_SetSwapInterval ((vid_vsync.value) ? 1 : 0) == -1) + gl_swap_control = false; + +#else /* !defined(USE_SDL2) */ + + flags = DEFAULT_SDL_FLAGS; + if (fullscreen) + flags |= SDL_FULLSCREEN; + if (vid_borderless.value) + flags |= SDL_NOFRAME; + + gl_swap_control = true; + if (SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, (vid_vsync.value) ? 1 : 0) == -1) + gl_swap_control = false; + + bpp = SDL_VideoModeOK(width, height, bpp, flags); + + draw_context = SDL_SetVideoMode(width, height, bpp, flags); + if (!draw_context) { // scale back fsaa + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + draw_context = SDL_SetVideoMode(width, height, bpp, flags); + } + if (!draw_context) { // scale back SDL_GL_DEPTH_SIZE + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); + draw_context = SDL_SetVideoMode(width, height, bpp, flags); + } + if (!draw_context) { // scale back SDL_GL_STENCIL_SIZE + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); + draw_context = SDL_SetVideoMode(width, height, bpp, flags); + if (!draw_context) + Sys_Error ("Couldn't set video mode"); + } + + SDL_WM_SetCaption(caption, caption); +#endif /* !defined(USE_SDL2) */ + + vid.width = VID_GetCurrentWidth(); + vid.height = VID_GetCurrentHeight(); + vid.conwidth = vid.width & 0xFFFFFFF8; + vid.conheight = vid.conwidth * vid.height / vid.width; + vid.numpages = 2; + +// read the obtained z-buffer depth + if (SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthbits) == -1) + depthbits = 0; + +// read obtained fsaa samples + if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fsaa_obtained) == -1) + fsaa_obtained = 0; + +// read stencil bits + if (SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &gl_stencilbits) == -1) + gl_stencilbits = 0; + + modestate = VID_GetFullscreen() ? MS_FULLSCREEN : MS_WINDOWED; + + CDAudio_Resume (); + BGM_Resume (); + scr_disabled_for_loading = temp; + +// fix the leftover Alt from any Alt-Tab or the like that switched us away + ClearAllStates (); + + Con_SafePrintf ("Video mode %dx%dx%d %dHz (%d-bit z-buffer, %dx FSAA) initialized\n", + VID_GetCurrentWidth(), + VID_GetCurrentHeight(), + VID_GetCurrentBPP(), + VID_GetCurrentRefreshRate(), + depthbits, + fsaa_obtained); + + vid.recalc_refdef = 1; + +// no pending changes + vid_changed = false; + + return true; +} + +/* +=================== +VID_Changed_f -- kristian -- notify us that a value has changed that requires a vid_restart +=================== +*/ +static void VID_Changed_f (cvar_t *var) +{ + vid_changed = true; +} + +/* +=================== +VID_Restart -- johnfitz -- change video modes on the fly +=================== +*/ +static void VID_Restart (void) +{ + int width, height, refreshrate, bpp; + qboolean fullscreen; + + if (vid_locked || !vid_changed) + return; + + width = (int)vid_width.value; + height = (int)vid_height.value; + refreshrate = (int)vid_refreshrate.value; + bpp = (int)vid_bpp.value; + fullscreen = vid_fullscreen.value ? true : false; + +// +// validate new mode +// + if (!VID_ValidMode (width, height, refreshrate, bpp, fullscreen)) + { + Con_Printf ("%dx%dx%d %dHz %s is not a valid mode\n", + width, height, bpp, refreshrate, fullscreen? "fullscreen" : "windowed"); + return; + } + +// ericw -- OS X, SDL1: textures, VBO's invalid after mode change +// OS X, SDL2: still valid after mode change +// To handle both cases, delete all GL objects (textures, VBO, GLSL) now. +// We must not interleave deleting the old objects with creating new ones, because +// one of the new objects could be given the same ID as an invalid handle +// which is later deleted. + + TexMgr_DeleteTextureObjects (); + GLSLGamma_DeleteTexture (); + R_ScaleView_DeleteTexture (); + R_DeleteShaders (); + GL_DeleteBModelVertexBuffer (); + GLMesh_DeleteVertexBuffers (); + +// +// set new mode +// + VID_SetMode (width, height, refreshrate, bpp, fullscreen); + + GL_Init (); + TexMgr_ReloadImages (); + GL_BuildBModelVertexBuffer (); + GLMesh_LoadVertexBuffers (); + GL_SetupState (); + Fog_SetupState (); + + //warpimages needs to be recalculated + TexMgr_RecalcWarpImageSize (); + + //conwidth and conheight need to be recalculated + vid.conwidth = (scr_conwidth.value > 0) ? (int)scr_conwidth.value : (scr_conscale.value > 0) ? (int)(vid.width/scr_conscale.value) : vid.width; + vid.conwidth = CLAMP (320, vid.conwidth, vid.width); + vid.conwidth &= 0xFFFFFFF8; + vid.conheight = vid.conwidth * vid.height / vid.width; +// +// keep cvars in line with actual mode +// + VID_SyncCvars(); +// +// update mouse grab +// + if (key_dest == key_console || key_dest == key_menu) + { + if (modestate == MS_WINDOWED) + IN_Deactivate(true); + else if (modestate == MS_FULLSCREEN) + IN_Activate(); + } +} + +/* +================ +VID_Test -- johnfitz -- like vid_restart, but asks for confirmation after switching modes +================ +*/ +static void VID_Test (void) +{ + int old_width, old_height, old_refreshrate, old_bpp, old_fullscreen; + + if (vid_locked || !vid_changed) + return; +// +// now try the switch +// + old_width = VID_GetCurrentWidth(); + old_height = VID_GetCurrentHeight(); + old_refreshrate = VID_GetCurrentRefreshRate(); + old_bpp = VID_GetCurrentBPP(); + old_fullscreen = VID_GetFullscreen() ? true : false; + + VID_Restart (); + + //pop up confirmation dialoge + if (!SCR_ModalMessage("Would you like to keep this\nvideo mode? (y/n)\n", 5.0f)) + { + //revert cvars and mode + Cvar_SetValueQuick (&vid_width, old_width); + Cvar_SetValueQuick (&vid_height, old_height); + Cvar_SetValueQuick (&vid_refreshrate, old_refreshrate); + Cvar_SetValueQuick (&vid_bpp, old_bpp); + Cvar_SetQuick (&vid_fullscreen, old_fullscreen ? "1" : "0"); + VID_Restart (); + } +} + +/* +================ +VID_Unlock -- johnfitz +================ +*/ +static void VID_Unlock (void) +{ + vid_locked = false; + VID_SyncCvars(); +} + +/* +================ +VID_Lock -- ericw + +Subsequent changes to vid_* mode settings, and vid_restart commands, will +be ignored until the "vid_unlock" command is run. + +Used when changing gamedirs so the current settings override what was saved +in the config.cfg. +================ +*/ +void VID_Lock (void) +{ + vid_locked = true; +} + +//============================================================================== +// +// OPENGL STUFF +// +//============================================================================== + +/* +=============== +GL_MakeNiceExtensionsList -- johnfitz +=============== +*/ +static char *GL_MakeNiceExtensionsList (const char *in) +{ + char *copy, *token, *out; + int i, count; + + if (!in) return Z_Strdup("(none)"); + + //each space will be replaced by 4 chars, so count the spaces before we malloc + for (i = 0, count = 1; i < (int) strlen(in); i++) + { + if (in[i] == ' ') + count++; + } + + out = (char *) Z_Malloc (strlen(in) + count*3 + 1); //usually about 1-2k + out[0] = 0; + + copy = (char *) Z_Strdup(in); + for (token = strtok(copy, " "); token; token = strtok(NULL, " ")) + { + strcat(out, "\n "); + strcat(out, token); + } + + Z_Free (copy); + return out; +} + +/* +=============== +GL_Info_f -- johnfitz +=============== +*/ +static void GL_Info_f (void) +{ + Con_SafePrintf ("GL_VENDOR: %s\n", gl_vendor); + Con_SafePrintf ("GL_RENDERER: %s\n", gl_renderer); + Con_SafePrintf ("GL_VERSION: %s\n", gl_version); + Con_Printf ("GL_EXTENSIONS: %s\n", gl_extensions_nice); +} + +/* +=============== +GL_CheckExtensions +=============== +*/ +static qboolean GL_ParseExtensionList (const char *list, const char *name) +{ + const char *start; + const char *where, *terminator; + + if (!list || !name || !*name) + return false; + if (strchr(name, ' ') != NULL) + return false; // extension names must not have spaces + + start = list; + while (1) { + where = strstr (start, name); + if (!where) + break; + terminator = where + strlen (name); + if (where == start || where[-1] == ' ') + if (*terminator == ' ' || *terminator == '\0') + return true; + start = terminator; + } + return false; +} + +static void GL_CheckExtensions (void) +{ + int swap_control; + + // ARB_vertex_buffer_object + // + if (COM_CheckParm("-novbo")) + Con_Warning ("Vertex buffer objects disabled at command line\n"); + else if (gl_version_major < 1 || (gl_version_major == 1 && gl_version_minor < 5)) + Con_Warning ("OpenGL version < 1.5, skipping ARB_vertex_buffer_object check\n"); + else + { + GL_BindBufferFunc = (PFNGLBINDBUFFERARBPROC) SDL_GL_GetProcAddress("glBindBufferARB"); + GL_BufferDataFunc = (PFNGLBUFFERDATAARBPROC) SDL_GL_GetProcAddress("glBufferDataARB"); + GL_BufferSubDataFunc = (PFNGLBUFFERSUBDATAARBPROC) SDL_GL_GetProcAddress("glBufferSubDataARB"); + GL_DeleteBuffersFunc = (PFNGLDELETEBUFFERSARBPROC) SDL_GL_GetProcAddress("glDeleteBuffersARB"); + GL_GenBuffersFunc = (PFNGLGENBUFFERSARBPROC) SDL_GL_GetProcAddress("glGenBuffersARB"); + if (GL_BindBufferFunc && GL_BufferDataFunc && GL_BufferSubDataFunc && GL_DeleteBuffersFunc && GL_GenBuffersFunc) + { + Con_Printf("FOUND: ARB_vertex_buffer_object\n"); + gl_vbo_able = true; + } + else + { + Con_Warning ("ARB_vertex_buffer_object not available\n"); + } + } + + // multitexture + // + if (COM_CheckParm("-nomtex")) + Con_Warning ("Mutitexture disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_multitexture")) + { + GL_MTexCoord2fFunc = (PFNGLMULTITEXCOORD2FARBPROC) SDL_GL_GetProcAddress("glMultiTexCoord2fARB"); + GL_SelectTextureFunc = (PFNGLACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glActiveTextureARB"); + GL_ClientActiveTextureFunc = (PFNGLCLIENTACTIVETEXTUREARBPROC) SDL_GL_GetProcAddress("glClientActiveTextureARB"); + if (GL_MTexCoord2fFunc && GL_SelectTextureFunc && GL_ClientActiveTextureFunc) + { + Con_Printf("FOUND: ARB_multitexture\n"); + gl_mtexable = true; + + glGetIntegerv(GL_MAX_TEXTURE_UNITS, &gl_max_texture_units); + Con_Printf("GL_MAX_TEXTURE_UNITS: %d\n", (int)gl_max_texture_units); + } + else + { + Con_Warning ("Couldn't link to multitexture functions\n"); + } + } + else + { + Con_Warning ("multitexture not supported (extension not found)\n"); + } + + // texture_env_combine + // + if (COM_CheckParm("-nocombine")) + Con_Warning ("texture_env_combine disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_texture_env_combine")) + { + Con_Printf("FOUND: ARB_texture_env_combine\n"); + gl_texture_env_combine = true; + } + else if (GL_ParseExtensionList(gl_extensions, "GL_EXT_texture_env_combine")) + { + Con_Printf("FOUND: EXT_texture_env_combine\n"); + gl_texture_env_combine = true; + } + else + { + Con_Warning ("texture_env_combine not supported\n"); + } + + // texture_env_add + // + if (COM_CheckParm("-noadd")) + Con_Warning ("texture_env_add disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_texture_env_add")) + { + Con_Printf("FOUND: ARB_texture_env_add\n"); + gl_texture_env_add = true; + } + else if (GL_ParseExtensionList(gl_extensions, "GL_EXT_texture_env_add")) + { + Con_Printf("FOUND: EXT_texture_env_add\n"); + gl_texture_env_add = true; + } + else + { + Con_Warning ("texture_env_add not supported\n"); + } + + // swap control + // + if (!gl_swap_control) + { +#if defined(USE_SDL2) + Con_Warning ("vertical sync not supported (SDL_GL_SetSwapInterval failed)\n"); +#else + Con_Warning ("vertical sync not supported (SDL_GL_SetAttribute failed)\n"); +#endif + } +#if defined(USE_SDL2) + else if ((swap_control = SDL_GL_GetSwapInterval()) == -1) +#else + else if (SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &swap_control) == -1) +#endif + { + gl_swap_control = false; +#if defined(USE_SDL2) + Con_Warning ("vertical sync not supported (SDL_GL_GetSwapInterval failed)\n"); +#else + Con_Warning ("vertical sync not supported (SDL_GL_GetAttribute failed)\n"); +#endif + } + else if ((vid_vsync.value && swap_control != 1) || (!vid_vsync.value && swap_control != 0)) + { + gl_swap_control = false; + Con_Warning ("vertical sync not supported (swap_control doesn't match vid_vsync)\n"); + } + else + { +#if defined(USE_SDL2) + Con_Printf("FOUND: SDL_GL_SetSwapInterval\n"); +#else + Con_Printf("FOUND: SDL_GL_SWAP_CONTROL\n"); +#endif + } + + // anisotropic filtering + // + if (GL_ParseExtensionList(gl_extensions, "GL_EXT_texture_filter_anisotropic")) + { + float test1,test2; + GLuint tex; + + // test to make sure we really have control over it + // 1.0 and 2.0 should always be legal values + glGenTextures(1, &tex); + glBindTexture (GL_TEXTURE_2D, tex); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + glGetTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &test1); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f); + glGetTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &test2); + glDeleteTextures(1, &tex); + + if (test1 == 1 && test2 == 2) + { + Con_Printf("FOUND: EXT_texture_filter_anisotropic\n"); + gl_anisotropy_able = true; + } + else + { + Con_Warning ("anisotropic filtering locked by driver. Current driver setting is %f\n", test1); + } + + //get max value either way, so the menu and stuff know it + glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl_max_anisotropy); + if (gl_max_anisotropy < 2) + { + gl_anisotropy_able = false; + gl_max_anisotropy = 1; + Con_Warning ("anisotropic filtering broken: disabled\n"); + } + } + else + { + gl_max_anisotropy = 1; + Con_Warning ("texture_filter_anisotropic not supported\n"); + } + + // texture_non_power_of_two + // + if (COM_CheckParm("-notexturenpot")) + Con_Warning ("texture_non_power_of_two disabled at command line\n"); + else if (GL_ParseExtensionList(gl_extensions, "GL_ARB_texture_non_power_of_two")) + { + Con_Printf("FOUND: ARB_texture_non_power_of_two\n"); + gl_texture_NPOT = true; + } + else + { + Con_Warning ("texture_non_power_of_two not supported\n"); + } + + // GLSL + // + if (COM_CheckParm("-noglsl")) + Con_Warning ("GLSL disabled at command line\n"); + else if (gl_version_major >= 2) + { + GL_CreateShaderFunc = (QS_PFNGLCREATESHADERPROC) SDL_GL_GetProcAddress("glCreateShader"); + GL_DeleteShaderFunc = (QS_PFNGLDELETESHADERPROC) SDL_GL_GetProcAddress("glDeleteShader"); + GL_DeleteProgramFunc = (QS_PFNGLDELETEPROGRAMPROC) SDL_GL_GetProcAddress("glDeleteProgram"); + GL_ShaderSourceFunc = (QS_PFNGLSHADERSOURCEPROC) SDL_GL_GetProcAddress("glShaderSource"); + GL_CompileShaderFunc = (QS_PFNGLCOMPILESHADERPROC) SDL_GL_GetProcAddress("glCompileShader"); + GL_GetShaderivFunc = (QS_PFNGLGETSHADERIVPROC) SDL_GL_GetProcAddress("glGetShaderiv"); + GL_GetShaderInfoLogFunc = (QS_PFNGLGETSHADERINFOLOGPROC) SDL_GL_GetProcAddress("glGetShaderInfoLog"); + GL_GetProgramivFunc = (QS_PFNGLGETPROGRAMIVPROC) SDL_GL_GetProcAddress("glGetProgramiv"); + GL_GetProgramInfoLogFunc = (QS_PFNGLGETPROGRAMINFOLOGPROC) SDL_GL_GetProcAddress("glGetProgramInfoLog"); + GL_CreateProgramFunc = (QS_PFNGLCREATEPROGRAMPROC) SDL_GL_GetProcAddress("glCreateProgram"); + GL_AttachShaderFunc = (QS_PFNGLATTACHSHADERPROC) SDL_GL_GetProcAddress("glAttachShader"); + GL_LinkProgramFunc = (QS_PFNGLLINKPROGRAMPROC) SDL_GL_GetProcAddress("glLinkProgram"); + GL_BindAttribLocationFunc = (QS_PFNGLBINDATTRIBLOCATIONFUNC) SDL_GL_GetProcAddress("glBindAttribLocation"); + GL_UseProgramFunc = (QS_PFNGLUSEPROGRAMPROC) SDL_GL_GetProcAddress("glUseProgram"); + GL_GetAttribLocationFunc = (QS_PFNGLGETATTRIBLOCATIONPROC) SDL_GL_GetProcAddress("glGetAttribLocation"); + GL_VertexAttribPointerFunc = (QS_PFNGLVERTEXATTRIBPOINTERPROC) SDL_GL_GetProcAddress("glVertexAttribPointer"); + GL_EnableVertexAttribArrayFunc = (QS_PFNGLENABLEVERTEXATTRIBARRAYPROC) SDL_GL_GetProcAddress("glEnableVertexAttribArray"); + GL_DisableVertexAttribArrayFunc = (QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC) SDL_GL_GetProcAddress("glDisableVertexAttribArray"); + GL_GetUniformLocationFunc = (QS_PFNGLGETUNIFORMLOCATIONPROC) SDL_GL_GetProcAddress("glGetUniformLocation"); + GL_Uniform1iFunc = (QS_PFNGLUNIFORM1IPROC) SDL_GL_GetProcAddress("glUniform1i"); + GL_Uniform1fFunc = (QS_PFNGLUNIFORM1FPROC) SDL_GL_GetProcAddress("glUniform1f"); + GL_Uniform3fFunc = (QS_PFNGLUNIFORM3FPROC) SDL_GL_GetProcAddress("glUniform3f"); + GL_Uniform4fFunc = (QS_PFNGLUNIFORM4FPROC) SDL_GL_GetProcAddress("glUniform4f"); + + if (GL_CreateShaderFunc && + GL_DeleteShaderFunc && + GL_DeleteProgramFunc && + GL_ShaderSourceFunc && + GL_CompileShaderFunc && + GL_GetShaderivFunc && + GL_GetShaderInfoLogFunc && + GL_GetProgramivFunc && + GL_GetProgramInfoLogFunc && + GL_CreateProgramFunc && + GL_AttachShaderFunc && + GL_LinkProgramFunc && + GL_BindAttribLocationFunc && + GL_UseProgramFunc && + GL_GetAttribLocationFunc && + GL_VertexAttribPointerFunc && + GL_EnableVertexAttribArrayFunc && + GL_DisableVertexAttribArrayFunc && + GL_GetUniformLocationFunc && + GL_Uniform1iFunc && + GL_Uniform1fFunc && + GL_Uniform3fFunc && + GL_Uniform4fFunc) + { + Con_Printf("FOUND: GLSL\n"); + gl_glsl_able = true; + } + else + { + Con_Warning ("GLSL not available\n"); + } + } + else + { + Con_Warning ("OpenGL version < 2, GLSL not available\n"); + } + // GLSL gamma + // + if (COM_CheckParm("-noglslgamma")) + Con_Warning ("GLSL gamma disabled at command line\n"); + else if (gl_glsl_able) + { + gl_glsl_gamma_able = true; + Con_Printf("Enabled: GLSL gamma\n"); + } + else + { + Con_Warning ("GLSL gamma not available, using hardware gamma\n"); + } + // GLSL alias model rendering + // + if (COM_CheckParm("-noglslalias")) + Con_Warning ("GLSL alias model rendering disabled at command line\n"); + else if (gl_glsl_able && gl_vbo_able && gl_max_texture_units >= 3) + { + gl_glsl_alias_able = true; + Con_Printf("Enabled: GLSL alias model rendering\n"); + } + else + { + Con_Warning ("GLSL alias model rendering not available, using Fitz renderer\n"); + } + + // packed_pixels + // + if (COM_CheckParm("-nopackedpixels")) + Con_Warning ("EXT_packed_pixels disabled at command line\n"); + else if (gl_glsl_alias_able) + { + gl_packed_pixels = true; + Con_Printf("Enabled: EXT_packed_pixels\n"); + } + #if 0 /* Disabling for non-GLSL path, needs more surgery. */ + else if (GL_ParseExtensionList(gl_extensions, "GL_APPLE_packed_pixels")) + { + Con_Printf("FOUND: APPLE_packed_pixels\n"); + gl_packed_pixels = true; + } + else if (GL_ParseExtensionList(gl_extensions, "GL_EXT_packed_pixels")) + { + Con_Printf("FOUND: EXT_packed_pixels\n"); + gl_packed_pixels = true; + } + else + { + Con_Warning ("packed_pixels not supported\n"); + } + #endif + + // glGenerateMipmap for warp textures + if (COM_CheckParm("-nowarpmipmaps")) + Con_Warning ("glGenerateMipmap disabled at command line\n"); + else + { + if (gl_version_major >= 3 || GL_ParseExtensionList(gl_extensions, "GL_ARB_framebuffer_object")) + { + GL_GenerateMipmap = (QS_PFNGENERATEMIPMAP) SDL_GL_GetProcAddress("glGenerateMipmap"); + if (GL_GenerateMipmap != NULL) + Con_Printf ("FOUND: glGenerateMipmap\n"); + } + else if (GL_ParseExtensionList(gl_extensions, "GL_EXT_framebuffer_object")) + { + GL_GenerateMipmap = (QS_PFNGENERATEMIPMAP) SDL_GL_GetProcAddress("glGenerateMipmapEXT"); + if (GL_GenerateMipmap != NULL) + Con_Printf ("FOUND: glGenerateMipmapEXT\n"); + } + if (GL_GenerateMipmap == NULL) + Con_Warning ("glGenerateMipmap not available, liquids won't have mipmaps\n"); + } +} + +/* +=============== +GL_SetupState -- johnfitz + +does all the stuff from GL_Init that needs to be done every time a new GL render context is created +=============== +*/ +static void GL_SetupState (void) +{ + glClearColor (0.15,0.15,0.15,0); //johnfitz -- originally 1,0,0,0 + glCullFace(GL_BACK); //johnfitz -- glquake used CCW with backwards culling -- let's do it right + glFrontFace(GL_CW); //johnfitz -- glquake used CCW with backwards culling -- let's do it right + glEnable(GL_TEXTURE_2D); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.666); + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glShadeModel (GL_FLAT); + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //johnfitz + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glDepthRange (0, 1); //johnfitz -- moved here becuase gl_ztrick is gone. + glDepthFunc (GL_LEQUAL); //johnfitz -- moved here becuase gl_ztrick is gone. +} + +/* +=============== +GL_Init +=============== +*/ +static void GL_Init (void) +{ + gl_vendor = (const char *) glGetString (GL_VENDOR); + gl_renderer = (const char *) glGetString (GL_RENDERER); + gl_version = (const char *) glGetString (GL_VERSION); + gl_extensions = (const char *) glGetString (GL_EXTENSIONS); + + Con_SafePrintf ("GL_VENDOR: %s\n", gl_vendor); + Con_SafePrintf ("GL_RENDERER: %s\n", gl_renderer); + Con_SafePrintf ("GL_VERSION: %s\n", gl_version); + + if (gl_version == NULL || sscanf(gl_version, "%d.%d", &gl_version_major, &gl_version_minor) < 2) + { + gl_version_major = 0; + gl_version_minor = 0; + } + + if (gl_extensions_nice != NULL) + Z_Free (gl_extensions_nice); + gl_extensions_nice = GL_MakeNiceExtensionsList (gl_extensions); + + GL_CheckExtensions (); //johnfitz + +#ifdef __APPLE__ + // ericw -- enable multi-threaded OpenGL, gives a decent FPS boost. + // https://developer.apple.com/library/mac/technotes/tn2085/ + if (host_parms->numcpus > 1 && + kCGLNoError != CGLEnable(CGLGetCurrentContext(), kCGLCEMPEngine)) + { + Con_Warning ("Couldn't enable multi-threaded OpenGL"); + } +#endif + + //johnfitz -- intel video workarounds from Baker + if (!strcmp(gl_vendor, "Intel")) + { + Con_Printf ("Intel Display Adapter detected, enabling gl_clear\n"); + Cbuf_AddText ("gl_clear 1"); + } + //johnfitz + + GLAlias_CreateShaders (); + GLWorld_CreateShaders (); + GL_ClearBufferBindings (); +} + +/* +================= +GL_BeginRendering -- sets values of glx, gly, glwidth, glheight +================= +*/ +void GL_BeginRendering (int *x, int *y, int *width, int *height) +{ + *x = *y = 0; + *width = vid.width; + *height = vid.height; +} + +/* +================= +GL_EndRendering +================= +*/ +void GL_EndRendering (void) +{ + if (!scr_skipupdate) + { +#if defined(USE_SDL2) + SDL_GL_SwapWindow(draw_context); +#else + SDL_GL_SwapBuffers(); +#endif + } +} + + +void VID_Shutdown (void) +{ + if (vid_initialized) + { + VID_Gamma_Shutdown (); //johnfitz +#if defined(USE_SDL2) + SDL_GL_DeleteContext(gl_context); + gl_context = NULL; + SDL_DestroyWindow(draw_context); +#endif + SDL_QuitSubSystem(SDL_INIT_VIDEO); + draw_context = NULL; + PL_VID_Shutdown(); + } +} + +/* +=================================================================== + +MAIN WINDOW + +=================================================================== +*/ + +/* +================ +ClearAllStates +================ +*/ +static void ClearAllStates (void) +{ + Key_ClearStates (); + IN_ClearStates (); +} + + +//========================================================================== +// +// COMMANDS +// +//========================================================================== + +/* +================= +VID_DescribeCurrentMode_f +================= +*/ +static void VID_DescribeCurrentMode_f (void) +{ + if (draw_context) + Con_Printf("%dx%dx%d %dHz %s\n", + VID_GetCurrentWidth(), + VID_GetCurrentHeight(), + VID_GetCurrentBPP(), + VID_GetCurrentRefreshRate(), + VID_GetFullscreen() ? "fullscreen" : "windowed"); +} + +/* +================= +VID_DescribeModes_f -- johnfitz -- changed formatting, and added refresh rates after each mode. +================= +*/ +static void VID_DescribeModes_f (void) +{ + int i; + int lastwidth, lastheight, lastbpp, count; + + lastwidth = lastheight = lastbpp = count = 0; + + for (i = 0; i < nummodes; i++) + { + if (lastwidth != modelist[i].width || lastheight != modelist[i].height || lastbpp != modelist[i].bpp) + { + if (count > 0) + Con_SafePrintf ("\n"); + Con_SafePrintf (" %4i x %4i x %i : %i", modelist[i].width, modelist[i].height, modelist[i].bpp, modelist[i].refreshrate); + lastwidth = modelist[i].width; + lastheight = modelist[i].height; + lastbpp = modelist[i].bpp; + count++; + } + } + Con_Printf ("\n%i modes\n", count); +} + +/* +=================== +VID_FSAA_f -- ericw -- warn that vid_fsaa requires engine restart +=================== +*/ +static void VID_FSAA_f (cvar_t *var) +{ + // don't print the warning if vid_fsaa is set during startup + if (vid_initialized) + Con_Printf("%s %d requires engine restart to take effect\n", var->name, (int)var->value); +} + +//========================================================================== +// +// INIT +// +//========================================================================== + +/* +================= +VID_InitModelist +================= +*/ +static void VID_InitModelist (void) +{ +#if defined(USE_SDL2) + const int sdlmodes = SDL_GetNumDisplayModes(0); + int i; + + nummodes = 0; + for (i = 0; i < sdlmodes; i++) + { + SDL_DisplayMode mode; + + if (nummodes >= MAX_MODE_LIST) + break; + if (SDL_GetDisplayMode(0, i, &mode) == 0) + { + modelist[nummodes].width = mode.w; + modelist[nummodes].height = mode.h; + modelist[nummodes].bpp = SDL_BITSPERPIXEL(mode.format); + modelist[nummodes].refreshrate = mode.refresh_rate; + nummodes++; + } + } +#else /* !defined(USE_SDL2) */ + SDL_PixelFormat format; + SDL_Rect **modes; + Uint32 flags; + int i, j, k, originalnummodes, existingmode; + int bpps[] = {16, 24, 32}; // enumerate >8 bpp modes + + originalnummodes = nummodes = 0; + memset(&format, 0, sizeof(format)); + + // enumerate fullscreen modes + flags = DEFAULT_SDL_FLAGS | SDL_FULLSCREEN; + for (i = 0; i < (int)Q_COUNTOF(bpps); i++) + { + if (nummodes >= MAX_MODE_LIST) + break; + + format.BitsPerPixel = bpps[i]; + modes = SDL_ListModes(&format, flags); + + if (modes == (SDL_Rect **)0 || modes == (SDL_Rect **)-1) + continue; + + for (j = 0; modes[j]; j++) + { + if (modes[j]->w > MAXWIDTH || modes[j]->h > MAXHEIGHT || nummodes >= MAX_MODE_LIST) + continue; + + modelist[nummodes].width = modes[j]->w; + modelist[nummodes].height = modes[j]->h; + modelist[nummodes].bpp = bpps[i]; + modelist[nummodes].refreshrate = DEFAULT_REFRESHRATE; + + for (k=originalnummodes, existingmode = 0 ; k < nummodes ; k++) + { + if ((modelist[nummodes].width == modelist[k].width) && + (modelist[nummodes].height == modelist[k].height) && + (modelist[nummodes].bpp == modelist[k].bpp)) + { + existingmode = 1; + break; + } + } + + if (!existingmode) + { + nummodes++; + } + } + } + + if (nummodes == originalnummodes) + Con_SafePrintf ("No fullscreen DIB modes found\n"); +#endif /* !defined(USE_SDL2) */ +} + +/* +=================== +VID_Init +=================== +*/ +void VID_Init (void) +{ + static char vid_center[] = "SDL_VIDEO_CENTERED=center"; + int p, width, height, refreshrate, bpp; + int display_width, display_height, display_refreshrate, display_bpp; + qboolean fullscreen; + const char *read_vars[] = { "vid_fullscreen", + "vid_width", + "vid_height", + "vid_refreshrate", + "vid_bpp", + "vid_vsync", + "vid_fsaa", + "vid_desktopfullscreen", + "vid_borderless"}; +#define num_readvars Q_COUNTOF(read_vars) + + Cvar_RegisterVariable (&vid_fullscreen); //johnfitz + Cvar_RegisterVariable (&vid_width); //johnfitz + Cvar_RegisterVariable (&vid_height); //johnfitz + Cvar_RegisterVariable (&vid_refreshrate); //johnfitz + Cvar_RegisterVariable (&vid_bpp); //johnfitz + Cvar_RegisterVariable (&vid_vsync); //johnfitz + Cvar_RegisterVariable (&vid_fsaa); //QuakeSpasm + Cvar_RegisterVariable (&vid_desktopfullscreen); //QuakeSpasm + Cvar_RegisterVariable (&vid_borderless); //QuakeSpasm + Cvar_SetCallback (&vid_fullscreen, VID_Changed_f); + Cvar_SetCallback (&vid_width, VID_Changed_f); + Cvar_SetCallback (&vid_height, VID_Changed_f); + Cvar_SetCallback (&vid_refreshrate, VID_Changed_f); + Cvar_SetCallback (&vid_bpp, VID_Changed_f); + Cvar_SetCallback (&vid_vsync, VID_Changed_f); + Cvar_SetCallback (&vid_fsaa, VID_FSAA_f); + Cvar_SetCallback (&vid_desktopfullscreen, VID_Changed_f); + Cvar_SetCallback (&vid_borderless, VID_Changed_f); + + Cmd_AddCommand ("vid_unlock", VID_Unlock); //johnfitz + Cmd_AddCommand ("vid_restart", VID_Restart); //johnfitz + Cmd_AddCommand ("vid_test", VID_Test); //johnfitz + Cmd_AddCommand ("vid_describecurrentmode", VID_DescribeCurrentMode_f); + Cmd_AddCommand ("vid_describemodes", VID_DescribeModes_f); + + putenv (vid_center); /* SDL_putenv is problematic in versions <= 1.2.9 */ + + if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) + Sys_Error("Couldn't init SDL video: %s", SDL_GetError()); + +#if defined(USE_SDL2) + { + SDL_DisplayMode mode; + if (SDL_GetDesktopDisplayMode(0, &mode) != 0) + Sys_Error("Could not get desktop display mode"); + + display_width = mode.w; + display_height = mode.h; + display_refreshrate = mode.refresh_rate; + display_bpp = SDL_BITSPERPIXEL(mode.format); + } +#else + { + const SDL_VideoInfo *info = SDL_GetVideoInfo(); + display_width = info->current_w; + display_height = info->current_h; + display_refreshrate = DEFAULT_REFRESHRATE; + display_bpp = info->vfmt->BitsPerPixel; + } +#endif + + Cvar_SetValueQuick (&vid_bpp, (float)display_bpp); + + if (CFG_OpenConfig("config.cfg") == 0) + { + CFG_ReadCvars(read_vars, num_readvars); + CFG_CloseConfig(); + } + CFG_ReadCvarOverrides(read_vars, num_readvars); + + VID_InitModelist(); + + width = (int)vid_width.value; + height = (int)vid_height.value; + refreshrate = (int)vid_refreshrate.value; + bpp = (int)vid_bpp.value; + fullscreen = (int)vid_fullscreen.value; + fsaa = (int)vid_fsaa.value; + + if (COM_CheckParm("-current")) + { + width = display_width; + height = display_height; + refreshrate = display_refreshrate; + bpp = display_bpp; + fullscreen = true; + } + else + { + p = COM_CheckParm("-width"); + if (p && p < com_argc-1) + { + width = Q_atoi(com_argv[p+1]); + + if(!COM_CheckParm("-height")) + height = width * 3 / 4; + } + + p = COM_CheckParm("-height"); + if (p && p < com_argc-1) + { + height = Q_atoi(com_argv[p+1]); + + if(!COM_CheckParm("-width")) + width = height * 4 / 3; + } + + p = COM_CheckParm("-refreshrate"); + if (p && p < com_argc-1) + refreshrate = Q_atoi(com_argv[p+1]); + + p = COM_CheckParm("-bpp"); + if (p && p < com_argc-1) + bpp = Q_atoi(com_argv[p+1]); + + if (COM_CheckParm("-window") || COM_CheckParm("-w")) + fullscreen = false; + else if (COM_CheckParm("-fullscreen") || COM_CheckParm("-f")) + fullscreen = true; + } + + p = COM_CheckParm ("-fsaa"); + if (p && p < com_argc-1) + fsaa = atoi(com_argv[p+1]); + + if (!VID_ValidMode(width, height, refreshrate, bpp, fullscreen)) + { + width = (int)vid_width.value; + height = (int)vid_height.value; + refreshrate = (int)vid_refreshrate.value; + bpp = (int)vid_bpp.value; + fullscreen = (int)vid_fullscreen.value; + } + + if (!VID_ValidMode(width, height, refreshrate, bpp, fullscreen)) + { + width = 640; + height = 480; + refreshrate = display_refreshrate; + bpp = display_bpp; + fullscreen = false; + } + + vid_initialized = true; + + vid.maxwarpwidth = WARP_WIDTH; + vid.maxwarpheight = WARP_HEIGHT; + vid.colormap = host_colormap; + vid.fullbright = 256 - LittleLong (*((int *)vid.colormap + 2048)); + +#if !defined(USE_SDL2) + // set window icon + PL_SetWindowIcon(); +#endif + + VID_SetMode (width, height, refreshrate, bpp, fullscreen); + +#if defined(USE_SDL2) + // set window icon + PL_SetWindowIcon(); +#endif + + GL_Init (); + GL_SetupState (); + Cmd_AddCommand ("gl_info", GL_Info_f); //johnfitz + + //johnfitz -- removed code creating "glquake" subdirectory + + vid_menucmdfn = VID_Menu_f; //johnfitz + vid_menudrawfn = VID_MenuDraw; + vid_menukeyfn = VID_MenuKey; + + VID_Gamma_Init(); //johnfitz + VID_Menu_Init(); //johnfitz + + //QuakeSpasm: current vid settings should override config file settings. + //so we have to lock the vid mode from now until after all config files are read. + vid_locked = true; +} + +// new proc by S.A., called by alt-return key binding. +void VID_Toggle (void) +{ + // disabling the fast path completely because SDL_SetWindowFullscreen was changing + // the window size on SDL2/WinXP and we weren't set up to handle it. --ericw + // + // TODO: Clear out the dead code, reinstate the fast path using SDL_SetWindowFullscreen + // inside VID_SetMode, check window size to fix WinXP issue. This will + // keep all the mode changing code in one place. + static qboolean vid_toggle_works = false; + qboolean toggleWorked; +#if defined(USE_SDL2) + Uint32 flags = 0; +#endif + + S_ClearBuffer (); + + if (!vid_toggle_works) + goto vrestart; + else if (gl_vbo_able) + { + // disabling the fast path because with SDL 1.2 it invalidates VBOs (using them + // causes a crash, sugesting that the fullscreen toggle created a new GL context, + // although texture objects remain valid for some reason). + // + // SDL2 does promise window resizes / fullscreen changes preserve the GL context, + // so we could use the fast path with SDL2. --ericw + vid_toggle_works = false; + goto vrestart; + } + +#if defined(USE_SDL2) + if (!VID_GetFullscreen()) { + flags = vid_desktopfullscreen.value ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; + } + + toggleWorked = SDL_SetWindowFullscreen(draw_context, flags) == 0; +#else + toggleWorked = SDL_WM_ToggleFullScreen(draw_context) == 1; +#endif + + if (toggleWorked) + { + Sbar_Changed (); // Sbar seems to need refreshing + + modestate = VID_GetFullscreen() ? MS_FULLSCREEN : MS_WINDOWED; + + VID_SyncCvars(); + + // update mouse grab + if (key_dest == key_console || key_dest == key_menu) + { + if (modestate == MS_WINDOWED) + IN_Deactivate(true); + else if (modestate == MS_FULLSCREEN) + IN_Activate(); + } + } + else + { + vid_toggle_works = false; + Con_DPrintf ("SDL_WM_ToggleFullScreen failed, attempting VID_Restart\n"); + vrestart: + Cvar_SetQuick (&vid_fullscreen, VID_GetFullscreen() ? "0" : "1"); + Cbuf_AddText ("vid_restart\n"); + } +} + +/* +================ +VID_SyncCvars -- johnfitz -- set vid cvars to match current video mode +================ +*/ +void VID_SyncCvars (void) +{ + if (draw_context) + { + if (!VID_GetDesktopFullscreen()) + { + Cvar_SetValueQuick (&vid_width, VID_GetCurrentWidth()); + Cvar_SetValueQuick (&vid_height, VID_GetCurrentHeight()); + } + Cvar_SetValueQuick (&vid_refreshrate, VID_GetCurrentRefreshRate()); + Cvar_SetValueQuick (&vid_bpp, VID_GetCurrentBPP()); + Cvar_SetQuick (&vid_fullscreen, VID_GetFullscreen() ? "1" : "0"); + // don't sync vid_desktopfullscreen, it's a user preference that + // should persist even if we are in windowed mode. + Cvar_SetQuick (&vid_vsync, VID_GetVSync() ? "1" : "0"); + } + + vid_changed = false; +} + +//========================================================================== +// +// NEW VIDEO MENU -- johnfitz +// +//========================================================================== + +enum { + VID_OPT_MODE, + VID_OPT_BPP, + VID_OPT_REFRESHRATE, + VID_OPT_FULLSCREEN, + VID_OPT_VSYNC, + VID_OPT_TEST, + VID_OPT_APPLY, + VIDEO_OPTIONS_ITEMS +}; + +static int video_options_cursor = 0; + +typedef struct { + int width,height; +} vid_menu_mode; + +//TODO: replace these fixed-length arrays with hunk_allocated buffers +static vid_menu_mode vid_menu_modes[MAX_MODE_LIST]; +static int vid_menu_nummodes = 0; + +static int vid_menu_bpps[MAX_BPPS_LIST]; +static int vid_menu_numbpps = 0; + +static int vid_menu_rates[MAX_RATES_LIST]; +static int vid_menu_numrates=0; + +/* +================ +VID_Menu_Init +================ +*/ +static void VID_Menu_Init (void) +{ + int i, j, h, w; + + for (i = 0; i < nummodes; i++) + { + w = modelist[i].width; + h = modelist[i].height; + + for (j = 0; j < vid_menu_nummodes; j++) + { + if (vid_menu_modes[j].width == w && + vid_menu_modes[j].height == h) + break; + } + + if (j == vid_menu_nummodes) + { + vid_menu_modes[j].width = w; + vid_menu_modes[j].height = h; + vid_menu_nummodes++; + } + } +} + +/* +================ +VID_Menu_RebuildBppList + +regenerates bpp list based on current vid_width and vid_height +================ +*/ +static void VID_Menu_RebuildBppList (void) +{ + int i, j, b; + + vid_menu_numbpps = 0; + + for (i = 0; i < nummodes; i++) + { + if (vid_menu_numbpps >= MAX_BPPS_LIST) + break; + + //bpp list is limited to bpps available with current width/height + if (modelist[i].width != vid_width.value || + modelist[i].height != vid_height.value) + continue; + + b = modelist[i].bpp; + + for (j = 0; j < vid_menu_numbpps; j++) + { + if (vid_menu_bpps[j] == b) + break; + } + + if (j == vid_menu_numbpps) + { + vid_menu_bpps[j] = b; + vid_menu_numbpps++; + } + } + + //if there are no valid fullscreen bpps for this width/height, just pick one + if (vid_menu_numbpps == 0) + { + Cvar_SetValueQuick (&vid_bpp, (float)modelist[0].bpp); + return; + } + + //if vid_bpp is not in the new list, change vid_bpp + for (i = 0; i < vid_menu_numbpps; i++) + if (vid_menu_bpps[i] == (int)(vid_bpp.value)) + break; + + if (i == vid_menu_numbpps) + Cvar_SetValueQuick (&vid_bpp, (float)vid_menu_bpps[0]); +} + +/* +================ +VID_Menu_RebuildRateList + +regenerates rate list based on current vid_width, vid_height and vid_bpp +================ +*/ +static void VID_Menu_RebuildRateList (void) +{ + int i, j, r; + + vid_menu_numrates = 0; + + for (i = 0; i < nummodes; i++) + { + //rate list is limited to rates available with current width/height/bpp + if (modelist[i].width != vid_width.value || + modelist[i].height != vid_height.value || + modelist[i].bpp != vid_bpp.value) + continue; + + r = modelist[i].refreshrate; + + for (j = 0; j < vid_menu_numrates; j++) + { + if (vid_menu_rates[j] == r) + break; + } + + if (j == vid_menu_numrates) + { + vid_menu_rates[j] = r; + vid_menu_numrates++; + } + } + + //if there are no valid fullscreen refreshrates for this width/height, just pick one + if (vid_menu_numrates == 0) + { + Cvar_SetValue ("vid_refreshrate",(float)modelist[0].refreshrate); + return; + } + + //if vid_refreshrate is not in the new list, change vid_refreshrate + for (i = 0; i < vid_menu_numrates; i++) + if (vid_menu_rates[i] == (int)(vid_refreshrate.value)) + break; + + if (i == vid_menu_numrates) + Cvar_SetValue ("vid_refreshrate",(float)vid_menu_rates[0]); +} + +/* +================ +VID_Menu_ChooseNextMode + +chooses next resolution in order, then updates vid_width and +vid_height cvars, then updates bpp and refreshrate lists +================ +*/ +static void VID_Menu_ChooseNextMode (int dir) +{ + int i; + + if (vid_menu_nummodes) + { + for (i = 0; i < vid_menu_nummodes; i++) + { + if (vid_menu_modes[i].width == vid_width.value && + vid_menu_modes[i].height == vid_height.value) + break; + } + + if (i == vid_menu_nummodes) //can't find it in list, so it must be a custom windowed res + { + i = 0; + } + else + { + i += dir; + if (i >= vid_menu_nummodes) + i = 0; + else if (i < 0) + i = vid_menu_nummodes-1; + } + + Cvar_SetValueQuick (&vid_width, (float)vid_menu_modes[i].width); + Cvar_SetValueQuick (&vid_height, (float)vid_menu_modes[i].height); + VID_Menu_RebuildBppList (); + VID_Menu_RebuildRateList (); + } +} + +/* +================ +VID_Menu_ChooseNextBpp + +chooses next bpp in order, then updates vid_bpp cvar +================ +*/ +static void VID_Menu_ChooseNextBpp (int dir) +{ + int i; + + if (vid_menu_numbpps) + { + for (i = 0; i < vid_menu_numbpps; i++) + { + if (vid_menu_bpps[i] == vid_bpp.value) + break; + } + + if (i == vid_menu_numbpps) //can't find it in list + { + i = 0; + } + else + { + i += dir; + if (i >= vid_menu_numbpps) + i = 0; + else if (i < 0) + i = vid_menu_numbpps-1; + } + + Cvar_SetValueQuick (&vid_bpp, (float)vid_menu_bpps[i]); + } +} + +/* +================ +VID_Menu_ChooseNextRate + +chooses next refresh rate in order, then updates vid_refreshrate cvar +================ +*/ +static void VID_Menu_ChooseNextRate (int dir) +{ + int i; + + for (i = 0; i < vid_menu_numrates; i++) + { + if (vid_menu_rates[i] == vid_refreshrate.value) + break; + } + + if (i == vid_menu_numrates) //can't find it in list + { + i = 0; + } + else + { + i += dir; + if (i >= vid_menu_numrates) + i = 0; + else if (i < 0) + i = vid_menu_numrates-1; + } + + Cvar_SetValue ("vid_refreshrate",(float)vid_menu_rates[i]); +} + +/* +================ +VID_MenuKey +================ +*/ +static void VID_MenuKey (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + VID_SyncCvars (); //sync cvars before leaving menu. FIXME: there are other ways to leave menu + S_LocalSound ("misc/menu1.wav"); + M_Menu_Options_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + video_options_cursor--; + if (video_options_cursor < 0) + video_options_cursor = VIDEO_OPTIONS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + video_options_cursor++; + if (video_options_cursor >= VIDEO_OPTIONS_ITEMS) + video_options_cursor = 0; + break; + + case K_LEFTARROW: + S_LocalSound ("misc/menu3.wav"); + switch (video_options_cursor) + { + case VID_OPT_MODE: + VID_Menu_ChooseNextMode (1); + break; + case VID_OPT_BPP: + VID_Menu_ChooseNextBpp (1); + break; + case VID_OPT_REFRESHRATE: + VID_Menu_ChooseNextRate (1); + break; + case VID_OPT_FULLSCREEN: + Cbuf_AddText ("toggle vid_fullscreen\n"); + break; + case VID_OPT_VSYNC: + Cbuf_AddText ("toggle vid_vsync\n"); // kristian + break; + default: + break; + } + break; + + case K_RIGHTARROW: + S_LocalSound ("misc/menu3.wav"); + switch (video_options_cursor) + { + case VID_OPT_MODE: + VID_Menu_ChooseNextMode (-1); + break; + case VID_OPT_BPP: + VID_Menu_ChooseNextBpp (-1); + break; + case VID_OPT_REFRESHRATE: + VID_Menu_ChooseNextRate (-1); + break; + case VID_OPT_FULLSCREEN: + Cbuf_AddText ("toggle vid_fullscreen\n"); + break; + case VID_OPT_VSYNC: + Cbuf_AddText ("toggle vid_vsync\n"); + break; + default: + break; + } + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + switch (video_options_cursor) + { + case VID_OPT_MODE: + VID_Menu_ChooseNextMode (1); + break; + case VID_OPT_BPP: + VID_Menu_ChooseNextBpp (1); + break; + case VID_OPT_REFRESHRATE: + VID_Menu_ChooseNextRate (1); + break; + case VID_OPT_FULLSCREEN: + Cbuf_AddText ("toggle vid_fullscreen\n"); + break; + case VID_OPT_VSYNC: + Cbuf_AddText ("toggle vid_vsync\n"); + break; + case VID_OPT_TEST: + Cbuf_AddText ("vid_test\n"); + break; + case VID_OPT_APPLY: + Cbuf_AddText ("vid_restart\n"); + key_dest = key_game; + m_state = m_none; + IN_Activate(); + break; + default: + break; + } + break; + + default: + break; + } +} + +/* +================ +VID_MenuDraw +================ +*/ +static void VID_MenuDraw (void) +{ + int i, y; + qpic_t *p; + const char *title; + + y = 4; + + // plaque + p = Draw_CachePic ("gfx/qplaque.lmp"); + M_DrawTransPic (16, y, p); + + //p = Draw_CachePic ("gfx/vidmodes.lmp"); + p = Draw_CachePic ("gfx/p_option.lmp"); + M_DrawPic ( (320-p->width)/2, y, p); + + y += 28; + + // title + title = "Video Options"; + M_PrintWhite ((320-8*strlen(title))/2, y, title); + + y += 16; + + // options + for (i = 0; i < VIDEO_OPTIONS_ITEMS; i++) + { + switch (i) + { + case VID_OPT_MODE: + M_Print (16, y, " Video mode"); + M_Print (184, y, va("%ix%i", (int)vid_width.value, (int)vid_height.value)); + break; + case VID_OPT_BPP: + M_Print (16, y, " Color depth"); + M_Print (184, y, va("%i", (int)vid_bpp.value)); + break; + case VID_OPT_REFRESHRATE: + M_Print (16, y, " Refresh rate"); + M_Print (184, y, va("%i", (int)vid_refreshrate.value)); + break; + case VID_OPT_FULLSCREEN: + M_Print (16, y, " Fullscreen"); + M_DrawCheckbox (184, y, (int)vid_fullscreen.value); + break; + case VID_OPT_VSYNC: + M_Print (16, y, " Vertical sync"); + if (gl_swap_control) + M_DrawCheckbox (184, y, (int)vid_vsync.value); + else + M_Print (184, y, "N/A"); + break; + case VID_OPT_TEST: + y += 8; //separate the test and apply items + M_Print (16, y, " Test changes"); + break; + case VID_OPT_APPLY: + M_Print (16, y, " Apply changes"); + break; + } + + if (video_options_cursor == i) + M_DrawCharacter (168, y, 12+((int)(realtime*4)&1)); + + y += 8; + } +} + +/* +================ +VID_Menu_f +================ +*/ +static void VID_Menu_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_video; + m_entersound = true; + + //set all the cvars to match the current mode when entering the menu + VID_SyncCvars (); + + //set up bpp and rate lists based on current cvars + VID_Menu_RebuildBppList (); + VID_Menu_RebuildRateList (); +} diff --git a/Quake/gl_warp.cpp b/Quake/gl_warp.cpp new file mode 100644 index 0000000..4ff327f --- /dev/null +++ b/Quake/gl_warp.cpp @@ -0,0 +1,277 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +//gl_warp.c -- warping animation support + +#include "quakedef.hpp" + +extern cvar_t r_drawflat; + +cvar_t r_oldwater = {"r_oldwater", "0", CVAR_ARCHIVE}; +cvar_t r_waterquality = {"r_waterquality", "8", CVAR_NONE}; +cvar_t r_waterwarp = {"r_waterwarp", "1", CVAR_NONE}; + +int gl_warpimagesize; +float load_subdivide_size; //johnfitz -- remember what subdivide_size value was when this map was loaded + +static const float turbsin[] = { +#include "gl_warp_sin.hpp" +}; + +#define WARPCALC(s,t) ((s + turbsin[(int)((t*2)+(cl.time*(128.0/M_PI))) & 255]) * (1.0/64)) //johnfitz -- correct warp +#define WARPCALC2(s,t) ((s + turbsin[(int)((t*0.125+cl.time)*(128.0/M_PI)) & 255]) * (1.0/64)) //johnfitz -- old warp + +//============================================================================== +// +// OLD-STYLE WATER +// +//============================================================================== + +static msurface_t *warpface; + +cvar_t gl_subdivide_size = {"gl_subdivide_size", "128", CVAR_ARCHIVE}; + +static void BoundPoly (int numverts, float *verts, vec3_t mins, vec3_t maxs) +{ + int i, j; + float *v; + + mins[0] = mins[1] = mins[2] = FLT_MAX; + maxs[0] = maxs[1] = maxs[2] = -FLT_MAX; + v = verts; + for (i=0 ; i maxs[j]) + maxs[j] = *v; + } +} + +static void SubdividePolygon (int numverts, float *verts) +{ + int i, j, k; + vec3_t mins, maxs; + float m; + float *v; + vec3_t front[64], back[64]; + int f, b; + float dist[64]; + float frac; + glpoly_t *poly; + float s, t; + + if (numverts > 60) + Sys_Error ("SubdividePolygon: numverts = %i", numverts); + + BoundPoly (numverts, verts, mins, maxs); + + for (i=0 ; i<3 ; i++) + { + m = (mins[i] + maxs[i]) * 0.5; + m = gl_subdivide_size.value * floor (m/gl_subdivide_size.value + 0.5); + if (maxs[i] - m < 8) + continue; + if (m - mins[i] < 8) + continue; + + // cut it + v = verts + i; + for (j=0 ; j= 0) + { + VectorCopy (v, front[f]); + f++; + } + if (dist[j] <= 0) + { + VectorCopy (v, back[b]); + b++; + } + if (dist[j] == 0 || dist[j+1] == 0) + continue; + if ( (dist[j] > 0) != (dist[j+1] > 0) ) + { + // clip point + frac = dist[j] / (dist[j] - dist[j+1]); + for (k=0 ; k<3 ; k++) + front[f][k] = back[b][k] = v[k] + frac*(v[3+k] - v[k]); + f++; + b++; + } + } + + SubdividePolygon (f, front[0]); + SubdividePolygon (b, back[0]); + return; + } + + poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (numverts-4) * VERTEXSIZE*sizeof(float)); + poly->next = warpface->polys->next; + warpface->polys->next = poly; + poly->numverts = numverts; + for (i=0 ; iverts[i]); + s = DotProduct (verts, warpface->texinfo->vecs[0]); + t = DotProduct (verts, warpface->texinfo->vecs[1]); + poly->verts[i][3] = s; + poly->verts[i][4] = t; + } +} + +/* +================ +GL_SubdivideSurface +================ +*/ +void GL_SubdivideSurface (msurface_t *fa) +{ + vec3_t verts[64]; + int i; + + if (fa->polys->numverts > 64) + Sys_Error ("GL_SubdivideSurface: numverts = %i", fa->polys->numverts); + + warpface = fa; + + //the first poly in the chain is the undivided poly for newwater rendering. + //grab the verts from that. + for (i=0; ipolys->numverts; i++) + VectorCopy (fa->polys->verts[i], verts[i]); + + SubdividePolygon (fa->polys->numverts, verts[0]); +} + +/* +================ +DrawWaterPoly -- johnfitz +================ +*/ +void DrawWaterPoly (glpoly_t *p) +{ + float *v; + int i; + + if (load_subdivide_size > 48) + { + glBegin (GL_POLYGON); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (WARPCALC2(v[3],v[4]), WARPCALC2(v[4],v[3])); + glVertex3fv (v); + } + glEnd (); + } + else + { + glBegin (GL_POLYGON); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (WARPCALC(v[3],v[4]), WARPCALC(v[4],v[3])); + glVertex3fv (v); + } + glEnd (); + } +} + +//============================================================================== +// +// RENDER-TO-FRAMEBUFFER WATER +// +//============================================================================== + +/* +============= +R_UpdateWarpTextures -- johnfitz -- each frame, update warping textures +============= +*/ +#ifdef __WATCOMC__ /* OW1.9 doesn't have floorf() */ +#define floorf(_val) (float)floor((_val)) +#endif +void R_UpdateWarpTextures (void) +{ + texture_t *tx; + int i; + float x, y, x2, warptess; + + if (r_oldwater.value || cl.paused || r_drawflat_cheatsafe || r_lightmap_cheatsafe) + return; + + warptess = 128.0f/CLAMP (3.0f, floorf(r_waterquality.value), 64.0f); + + for (i=0; inumtextures; i++) + { + if (!(tx = cl.worldmodel->textures[i])) + continue; + + if (!tx->update_warp) + continue; + + //render warp + GL_SetCanvas (CANVAS_WARPIMAGE); + GL_Bind (tx->gltexture); + for (x=0.0; x<128.0; x=x2) + { + x2 = x + warptess; + glBegin (GL_TRIANGLE_STRIP); + for (y=0.0; y<128.01; y+=warptess) // .01 for rounding errors + { + glTexCoord2f (WARPCALC(x,y), WARPCALC(y,x)); + glVertex2f (x,y); + glTexCoord2f (WARPCALC(x2,y), WARPCALC(y,x2)); + glVertex2f (x2,y); + } + glEnd(); + } + + //copy to texture + GL_Bind (tx->warpimage); + glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, glx, gly+glheight-gl_warpimagesize, gl_warpimagesize, gl_warpimagesize); + if (GL_GenerateMipmap) + GL_GenerateMipmap (GL_TEXTURE_2D); + + tx->update_warp = false; + } + + // ericw -- workaround for osx 10.6 driver bug when using FSAA. R_Clear only clears the warpimage part of the screen. + GL_SetCanvas(CANVAS_DEFAULT); + + //if warp render went down into sbar territory, we need to be sure to refresh it next frame + if (gl_warpimagesize + sb_lines > glheight) + Sbar_Changed (); + + //if viewsize is less than 100, we need to redraw the frame around the viewport + scr_tileclear_updates = 0; +} diff --git a/Quake/gl_warp_sin.hpp b/Quake/gl_warp_sin.hpp new file mode 100644 index 0000000..313ab4e --- /dev/null +++ b/Quake/gl_warp_sin.hpp @@ -0,0 +1,84 @@ +/* + * gl_warp_sin.h + * Copyright (C) 1996-1997 Id Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + 0, 0.19633, 0.392541, 0.588517, + 0.784137, 0.979285, 1.17384, 1.3677, + 1.56072, 1.75281, 1.94384, 2.1337, + 2.32228, 2.50945, 2.69512, 2.87916, + 3.06147, 3.24193, 3.42044, 3.59689, + 3.77117, 3.94319, 4.11282, 4.27998, + 4.44456, 4.60647, 4.76559, 4.92185, + 5.07515, 5.22538, 5.37247, 5.51632, + 5.65685, 5.79398, 5.92761, 6.05767, + 6.18408, 6.30677, 6.42566, 6.54068, + 6.65176, 6.75883, 6.86183, 6.9607, + 7.05537, 7.14579, 7.23191, 7.31368, + 7.39104, 7.46394, 7.53235, 7.59623, + 7.65552, 7.71021, 7.76025, 7.80562, + 7.84628, 7.88222, 7.91341, 7.93984, + 7.96148, 7.97832, 7.99036, 7.99759, + 8, 7.99759, 7.99036, 7.97832, + 7.96148, 7.93984, 7.91341, 7.88222, + 7.84628, 7.80562, 7.76025, 7.71021, + 7.65552, 7.59623, 7.53235, 7.46394, + 7.39104, 7.31368, 7.23191, 7.14579, + 7.05537, 6.9607, 6.86183, 6.75883, + 6.65176, 6.54068, 6.42566, 6.30677, + 6.18408, 6.05767, 5.92761, 5.79398, + 5.65685, 5.51632, 5.37247, 5.22538, + 5.07515, 4.92185, 4.76559, 4.60647, + 4.44456, 4.27998, 4.11282, 3.94319, + 3.77117, 3.59689, 3.42044, 3.24193, + 3.06147, 2.87916, 2.69512, 2.50945, + 2.32228, 2.1337, 1.94384, 1.75281, + 1.56072, 1.3677, 1.17384, 0.979285, + 0.784137, 0.588517, 0.392541, 0.19633, + 9.79717e-16, -0.19633, -0.392541, -0.588517, + -0.784137, -0.979285, -1.17384, -1.3677, + -1.56072, -1.75281, -1.94384, -2.1337, + -2.32228, -2.50945, -2.69512, -2.87916, + -3.06147, -3.24193, -3.42044, -3.59689, + -3.77117, -3.94319, -4.11282, -4.27998, + -4.44456, -4.60647, -4.76559, -4.92185, + -5.07515, -5.22538, -5.37247, -5.51632, + -5.65685, -5.79398, -5.92761, -6.05767, + -6.18408, -6.30677, -6.42566, -6.54068, + -6.65176, -6.75883, -6.86183, -6.9607, + -7.05537, -7.14579, -7.23191, -7.31368, + -7.39104, -7.46394, -7.53235, -7.59623, + -7.65552, -7.71021, -7.76025, -7.80562, + -7.84628, -7.88222, -7.91341, -7.93984, + -7.96148, -7.97832, -7.99036, -7.99759, + -8, -7.99759, -7.99036, -7.97832, + -7.96148, -7.93984, -7.91341, -7.88222, + -7.84628, -7.80562, -7.76025, -7.71021, + -7.65552, -7.59623, -7.53235, -7.46394, + -7.39104, -7.31368, -7.23191, -7.14579, + -7.05537, -6.9607, -6.86183, -6.75883, + -6.65176, -6.54068, -6.42566, -6.30677, + -6.18408, -6.05767, -5.92761, -5.79398, + -5.65685, -5.51632, -5.37247, -5.22538, + -5.07515, -4.92185, -4.76559, -4.60647, + -4.44456, -4.27998, -4.11282, -3.94319, + -3.77117, -3.59689, -3.42044, -3.24193, + -3.06147, -2.87916, -2.69512, -2.50945, + -2.32228, -2.1337, -1.94384, -1.75281, + -1.56072, -1.3677, -1.17384, -0.979285, + -0.784137, -0.588517, -0.392541, -0.19633, + diff --git a/Quake/glquake.hpp b/Quake/glquake.hpp new file mode 100644 index 0000000..49f918d --- /dev/null +++ b/Quake/glquake.hpp @@ -0,0 +1,439 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef GLQUAKE_H +#define GLQUAKE_H + +void GL_BeginRendering (int *x, int *y, int *width, int *height); +void GL_EndRendering (void); +void GL_Set2D (void); + +extern int glx, gly, glwidth, glheight; + +#define GL_UNUSED_TEXTURE (~(GLuint)0) + +// r_local.h -- private refresh defs + +#define ALIAS_BASE_SIZE_RATIO (1.0 / 11.0) + // normalizing factor so player model works out to about + // 1 pixel per triangle +#define MAX_LBM_HEIGHT 480 + +#define TILE_SIZE 128 // size of textures generated by R_GenTiledSurf + +#define SKYSHIFT 7 +#define SKYSIZE (1 << SKYSHIFT) +#define SKYMASK (SKYSIZE - 1) + +#define BACKFACE_EPSILON 0.01 + + +void R_TimeRefresh_f (void); +void R_ReadPointFile_f (void); +texture_t *R_TextureAnimation (texture_t *base, int frame); + +typedef struct surfcache_s +{ + struct surfcache_s *next; + struct surfcache_s **owner; // NULL is an empty chunk of memory + int lightadj[MAXLIGHTMAPS]; // checked for strobe flush + int dlight; + int size; // including header + unsigned width; + unsigned height; // DEBUG only needed for debug + float mipscale; + struct texture_s *texture; // checked for animating textures + byte data[4]; // width*height elements +} surfcache_t; + + +typedef struct +{ + pixel_t *surfdat; // destination for generated surface + int rowbytes; // destination logical width in bytes + msurface_t *surf; // description for surface to generate + fixed8_t lightadj[MAXLIGHTMAPS]; + // adjust for lightmap levels for dynamic lighting + texture_t *texture; // corrected for animating textures + int surfmip; // mipmapped ratio of surface texels / world pixels + int surfwidth; // in mipmapped texels + int surfheight; // in mipmapped texels +} drawsurf_t; + + +typedef enum { + pt_static, pt_grav, pt_slowgrav, pt_fire, pt_explode, pt_explode2, pt_blob, pt_blob2 +} ptype_t; + +// !!! if this is changed, it must be changed in d_ifacea.h too !!! +typedef struct particle_s +{ +// driver-usable fields + vec3_t org; + float color; +// drivers never touch the following fields + struct particle_s *next; + vec3_t vel; + float ramp; + float die; + ptype_t type; +} particle_t; + + +//==================================================== + +extern vec3_t modelorg, r_entorigin; +extern entity_t *currententity; +extern int r_visframecount; // ??? what difs? +extern int r_framecount; +extern mplane_t frustum[4]; + +extern texture_t *r_notexture_mip, *r_notexture_mip2; + +// +// view origin +// +extern vec3_t vup; +extern vec3_t vpn; +extern vec3_t vright; +extern vec3_t r_origin; + +// +// screen size info +// +extern refdef_t r_refdef; +extern mleaf_t *r_viewleaf, *r_oldviewleaf; +extern int d_lightstylevalue[256]; // 8.8 fraction of base light value + +extern cvar_t r_norefresh; +extern cvar_t r_drawentities; +extern cvar_t r_drawworld; +extern cvar_t r_drawviewmodel; +extern cvar_t r_speeds; +extern cvar_t r_pos; +extern cvar_t r_waterwarp; +extern cvar_t r_fullbright; +extern cvar_t r_lightmap; +extern cvar_t r_lightmapwide; +extern cvar_t r_shadows; +extern cvar_t r_wateralpha; +extern cvar_t r_lavaalpha; +extern cvar_t r_telealpha; +extern cvar_t r_slimealpha; +extern cvar_t r_litwater; +extern cvar_t r_dynamic; +extern cvar_t r_novis; +extern cvar_t r_scale; + +extern cvar_t gl_clear; +extern cvar_t gl_cull; +extern cvar_t gl_smoothmodels; +extern cvar_t gl_affinemodels; +extern cvar_t gl_polyblend; +extern cvar_t gl_flashblend; +extern cvar_t gl_nocolors; +extern cvar_t gl_finish; + +extern cvar_t gl_playermip; + +extern cvar_t gl_subdivide_size; +extern float load_subdivide_size; //johnfitz -- remember what subdivide_size value was when this map was loaded + +extern int gl_stencilbits; + +// Multitexture +extern qboolean mtexenabled; +extern qboolean gl_mtexable; +extern PFNGLMULTITEXCOORD2FARBPROC GL_MTexCoord2fFunc; +extern PFNGLACTIVETEXTUREARBPROC GL_SelectTextureFunc; +extern PFNGLCLIENTACTIVETEXTUREARBPROC GL_ClientActiveTextureFunc; +extern GLint gl_max_texture_units; //ericw + +//johnfitz -- anisotropic filtering +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +extern float gl_max_anisotropy; +extern qboolean gl_anisotropy_able; + +//ericw -- VBO +extern PFNGLBINDBUFFERARBPROC GL_BindBufferFunc; +extern PFNGLBUFFERDATAARBPROC GL_BufferDataFunc; +extern PFNGLBUFFERSUBDATAARBPROC GL_BufferSubDataFunc; +extern PFNGLDELETEBUFFERSARBPROC GL_DeleteBuffersFunc; +extern PFNGLGENBUFFERSARBPROC GL_GenBuffersFunc; +extern qboolean gl_vbo_able; +//ericw + +//ericw -- GLSL + +// SDL 1.2 has a bug where it doesn't provide these typedefs on OS X! +typedef GLuint (APIENTRYP QS_PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP QS_PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP QS_PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP QS_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP QS_PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef void (APIENTRYP QS_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP QS_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP QS_PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP QS_PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLuint (APIENTRYP QS_PFNGLCREATEPROGRAMPROC) (void); +typedef void (APIENTRYP QS_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP QS_PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP QS_PFNGLBINDATTRIBLOCATIONFUNC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLUSEPROGRAMPROC) (GLuint program); +typedef GLint (APIENTRYP QS_PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (APIENTRYP QS_PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef GLint (APIENTRYP QS_PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP QS_PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP QS_PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP QS_PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP QS_PFNGENERATEMIPMAP) (GLenum type); + +extern QS_PFNGLCREATESHADERPROC GL_CreateShaderFunc; +extern QS_PFNGLDELETESHADERPROC GL_DeleteShaderFunc; +extern QS_PFNGLDELETEPROGRAMPROC GL_DeleteProgramFunc; +extern QS_PFNGLSHADERSOURCEPROC GL_ShaderSourceFunc; +extern QS_PFNGLCOMPILESHADERPROC GL_CompileShaderFunc; +extern QS_PFNGLGETSHADERIVPROC GL_GetShaderivFunc; +extern QS_PFNGLGETSHADERINFOLOGPROC GL_GetShaderInfoLogFunc; +extern QS_PFNGLGETPROGRAMIVPROC GL_GetProgramivFunc; +extern QS_PFNGLGETPROGRAMINFOLOGPROC GL_GetProgramInfoLogFunc; +extern QS_PFNGLCREATEPROGRAMPROC GL_CreateProgramFunc; +extern QS_PFNGLATTACHSHADERPROC GL_AttachShaderFunc; +extern QS_PFNGLLINKPROGRAMPROC GL_LinkProgramFunc; +extern QS_PFNGLBINDATTRIBLOCATIONFUNC GL_BindAttribLocationFunc; +extern QS_PFNGLUSEPROGRAMPROC GL_UseProgramFunc; +extern QS_PFNGLGETATTRIBLOCATIONPROC GL_GetAttribLocationFunc; +extern QS_PFNGLVERTEXATTRIBPOINTERPROC GL_VertexAttribPointerFunc; +extern QS_PFNGLENABLEVERTEXATTRIBARRAYPROC GL_EnableVertexAttribArrayFunc; +extern QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC GL_DisableVertexAttribArrayFunc; +extern QS_PFNGLGETUNIFORMLOCATIONPROC GL_GetUniformLocationFunc; +extern QS_PFNGLUNIFORM1IPROC GL_Uniform1iFunc; +extern QS_PFNGLUNIFORM1FPROC GL_Uniform1fFunc; +extern QS_PFNGLUNIFORM3FPROC GL_Uniform3fFunc; +extern QS_PFNGLUNIFORM4FPROC GL_Uniform4fFunc; +extern qboolean gl_glsl_able; +extern qboolean gl_glsl_gamma_able; +extern qboolean gl_glsl_alias_able; +// ericw -- + +//mipmapped warp textures +extern QS_PFNGENERATEMIPMAP GL_GenerateMipmap; + +//ericw -- NPOT texture support +extern qboolean gl_texture_NPOT; + +//johnfitz -- polygon offset +#define OFFSET_BMODEL 1 +#define OFFSET_NONE 0 +#define OFFSET_DECAL -1 +#define OFFSET_FOG -2 +#define OFFSET_SHOWTRIS -3 +void GL_PolygonOffset (int); + +//GL_EXT_packed_pixels +#ifndef GL_UNSIGNED_INT_10_10_10_2 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#endif +extern qboolean gl_packed_pixels; + +//johnfitz -- GL_EXT_texture_env_combine +//the values for GL_ARB_ are identical +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +extern qboolean gl_texture_env_combine; +extern qboolean gl_texture_env_add; // for GL_EXT_texture_env_add + +//johnfitz -- rendering statistics +extern int rs_brushpolys, rs_aliaspolys, rs_skypolys; +extern int rs_dynamiclightmaps, rs_brushpasses, rs_aliaspasses, rs_skypasses; + +//johnfitz -- track developer statistics that vary every frame +extern cvar_t devstats; +typedef struct { + int packetsize; + int edicts; + int visedicts; + int efrags; + int tempents; + int beams; + int dlights; +} devstats_t; +extern devstats_t dev_stats, dev_peakstats; + +//ohnfitz -- reduce overflow warning spam +typedef struct { + double packetsize; + double efrags; + double beams; + double varstring; +} overflowtimes_t; +extern overflowtimes_t dev_overflows; //this stores the last time overflow messages were displayed, not the last time overflows occured +#define CONSOLE_RESPAM_TIME 3 // seconds between repeated warning messages + +//johnfitz -- moved here from r_brush.c +extern int gl_lightmap_format, lightmap_bytes; + +#define LMBLOCK_WIDTH 256 //FIXME: make dynamic. if we have a decent card there's no real reason not to use 4k or 16k (assuming there's no lightstyles/dynamics that need uploading...) +#define LMBLOCK_HEIGHT 256 //Alternatively, use texture arrays, which would avoid the need to switch textures as often. + +typedef struct glRect_s { + unsigned short l,t,w,h; +} glRect_t; +struct lightmap_s +{ + gltexture_t *texture; + glpoly_t *polys; + qboolean modified; + glRect_t rectchange; + + // the lightmap texture data needs to be kept in + // main memory so texsubimage can update properly + byte *data;//[4*LMBLOCK_WIDTH*LMBLOCK_HEIGHT]; +}; +extern struct lightmap_s *lightmaps; +extern int lightmap_count; //allocated lightmaps + +extern int gl_warpimagesize; //johnfitz -- for water warp + +extern qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz + +typedef struct glsl_attrib_binding_s { + const char *name; + GLuint attrib; +} glsl_attrib_binding_t; + +extern float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha; //ericw + +#define NUMVERTEXNORMALS 162 +extern const float r_avertexnormals[NUMVERTEXNORMALS][3]; + +//johnfitz -- fog functions called from outside gl_fog.c +void Fog_ParseServerMessage (void); +float *Fog_GetColor (void); +float Fog_GetDensity (void); +void Fog_EnableGFog (void); +void Fog_DisableGFog (void); +void Fog_StartAdditive (void); +void Fog_StopAdditive (void); +void Fog_SetupFrame (void); +void Fog_NewMap (void); +void Fog_Init (void); +void Fog_SetupState (void); + +void R_NewGame (void); + +void R_AnimateLight (void); +void R_MarkSurfaces (void); +qboolean R_CullBox (vec3_t emins, vec3_t emaxs); +void R_StoreEfrags (efrag_t **ppefrag); +qboolean R_CullModelForEntity (entity_t *e); +void R_RotateForEntity (vec3_t origin, vec3_t angles, unsigned char scale); +void R_MarkLights (dlight_t *light, int num, mnode_t *node); + +void R_InitParticles (void); +void R_DrawParticles (void); +void CL_RunParticles (void); +void R_ClearParticles (void); + +void R_TranslatePlayerSkin (int playernum); +void R_TranslateNewPlayerSkin (int playernum); //johnfitz -- this handles cases when the actual texture changes +void R_UpdateWarpTextures (void); + +void R_DrawWorld (void); +void R_DrawAliasModel (entity_t *e); +void R_DrawBrushModel (entity_t *e); +void R_DrawSpriteModel (entity_t *e); + +void R_DrawTextureChains_Water (qmodel_t *model, entity_t *ent, texchain_t chain); + +void R_RenderDlights (void); +void GL_BuildLightmaps (void); +void GL_DeleteBModelVertexBuffer (void); +void GL_BuildBModelVertexBuffer (void); +void GLMesh_LoadVertexBuffers (void); +void GLMesh_DeleteVertexBuffers (void); +void R_RebuildAllLightmaps (void); + +int R_LightPoint (vec3_t p); + +void GL_SubdivideSurface (msurface_t *fa); +void R_BuildLightMap (msurface_t *surf, byte *dest, int stride); +void R_RenderDynamicLightmaps (msurface_t *fa); +void R_UploadLightmaps (void); + +void R_DrawWorld_ShowTris (void); +void R_DrawBrushModel_ShowTris (entity_t *e); +void R_DrawAliasModel_ShowTris (entity_t *e); +void R_DrawParticles_ShowTris (void); + +GLint GL_GetUniformLocation (GLuint *programPtr, const char *name); +GLuint GL_CreateProgram (const GLchar *vertSource, const GLchar *fragSource, int numbindings, const glsl_attrib_binding_t *bindings); +void R_DeleteShaders (void); + +void GLWorld_CreateShaders (void); +void GLAlias_CreateShaders (void); +void GL_DrawAliasShadow (entity_t *e); +void DrawGLTriangleFan (glpoly_t *p); +void DrawGLPoly (glpoly_t *p); +void DrawWaterPoly (glpoly_t *p); +void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr); + +void Sky_Init (void); +void Sky_ClearAll (void); +void Sky_DrawSky (void); +void Sky_NewMap (void); +void Sky_LoadTexture (qmodel_t *m, texture_t *mt); +void Sky_LoadTextureQ64 (qmodel_t *m, texture_t *mt); +void Sky_LoadSkyBox (const char *name); + +void TexMgr_RecalcWarpImageSize (void); + +void R_ClearTextureChains (qmodel_t *mod, texchain_t chain); +void R_ChainSurface (msurface_t *surf, texchain_t chain); +void R_DrawTextureChains (qmodel_t *model, entity_t *ent, texchain_t chain); +void R_DrawWorld_Water (void); + +void GL_BindBuffer (GLenum target, GLuint buffer); +void GL_ClearBufferBindings (void); + +void GLSLGamma_DeleteTexture (void); +void GLSLGamma_GammaCorrect (void); + +void R_ScaleView_DeleteTexture (void); + +float GL_WaterAlphaForSurface (msurface_t *fa); + +#endif /* GLQUAKE_H */ diff --git a/Quake/host.cpp b/Quake/host.cpp new file mode 100644 index 0000000..7cf73f4 --- /dev/null +++ b/Quake/host.cpp @@ -0,0 +1,947 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// host.c -- coordinates spawning and killing of local servers + +#include "quakedef.hpp" +#include "bgmusic.hpp" +#include + +/* + +A server can allways be started, even if the system started out as a client +to a remote system. + +A client can NOT be started if the system started as a dedicated server. + +Memory is cleared / released when a server or client begins, not when they end. + +*/ + +quakeparms_t *host_parms; + +qboolean host_initialized; // true if into command execution + +double host_frametime; +double realtime; // without any filtering or bounding +double oldrealtime; // last frame run + +int host_framecount; + +int host_hunklevel; + +int minimum_memory; + +client_t *host_client; // current client + +jmp_buf host_abortserver; + +byte *host_colormap; + +cvar_t host_framerate = {"host_framerate","0",CVAR_NONE}; // set for slow motion +cvar_t host_speeds = {"host_speeds","0",CVAR_NONE}; // set for running times +cvar_t host_maxfps = {"host_maxfps", "72", CVAR_ARCHIVE}; //johnfitz +cvar_t host_timescale = {"host_timescale", "0", CVAR_NONE}; //johnfitz +cvar_t max_edicts = {"max_edicts", "8192", CVAR_NONE}; //johnfitz //ericw -- changed from 2048 to 8192, removed CVAR_ARCHIVE + +cvar_t sys_ticrate = {"sys_ticrate","0.05",CVAR_NONE}; // dedicated server +cvar_t serverprofile = {"serverprofile","0",CVAR_NONE}; + +cvar_t fraglimit = {"fraglimit","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t timelimit = {"timelimit","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t teamplay = {"teamplay","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t samelevel = {"samelevel","0",CVAR_NONE}; +cvar_t noexit = {"noexit","0",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t skill = {"skill","1",CVAR_NONE}; // 0 - 3 +cvar_t deathmatch = {"deathmatch","0",CVAR_NONE}; // 0, 1, or 2 +cvar_t coop = {"coop","0",CVAR_NONE}; // 0 or 1 + +cvar_t pausable = {"pausable","1",CVAR_NONE}; + +cvar_t developer = {"developer","0",CVAR_NONE}; + +cvar_t temp1 = {"temp1","0",CVAR_NONE}; + +cvar_t devstats = {"devstats","0",CVAR_NONE}; //johnfitz -- track developer statistics that vary every frame + +cvar_t campaign = {"campaign","0",CVAR_NONE}; // for the 2021 rerelease +cvar_t horde = {"horde","0",CVAR_NONE}; // for the 2021 rerelease +cvar_t sv_cheats = {"sv_cheats","0",CVAR_NONE}; // for the 2021 rerelease + +devstats_t dev_stats, dev_peakstats; +overflowtimes_t dev_overflows; //this stores the last time overflow messages were displayed, not the last time overflows occured + +/* +================ +Max_Edicts_f -- johnfitz +================ +*/ +static void Max_Edicts_f (cvar_t *var) +{ + //TODO: clamp it here? + if (cls.state == ca_connected || sv.active) + Con_Printf ("Changes to max_edicts will not take effect until the next time a map is loaded.\n"); +} + +/* +================ +Max_Fps_f -- ericw +================ +*/ +static void Max_Fps_f (cvar_t *var) +{ + if (var->value > 72) + Con_Warning ("host_maxfps above 72 breaks physics.\n"); +} + +/* +================ +Host_EndGame +================ +*/ +void Host_EndGame (const char *message, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,message); + q_vsnprintf (string, sizeof(string), message, argptr); + va_end (argptr); + Con_DPrintf ("Host_EndGame: %s\n",string); + + if (sv.active) + Host_ShutdownServer (false); + + if (cls.state == ca_dedicated) + Sys_Error ("Host_EndGame: %s\n",string); // dedicated servers exit + + if (cls.demonum != -1 && !cls.timedemo) + CL_NextDemo (); + else + CL_Disconnect (); + + longjmp (host_abortserver, 1); +} + +/* +================ +Host_Error + +This shuts down both the client and server +================ +*/ +void Host_Error (const char *error, ...) +{ + va_list argptr; + char string[1024]; + static qboolean inerror = false; + + if (inerror) + Sys_Error ("Host_Error: recursively entered"); + inerror = true; + + SCR_EndLoadingPlaque (); // reenable screen updates + + va_start (argptr,error); + q_vsnprintf (string, sizeof(string), error, argptr); + va_end (argptr); + Con_Printf ("Host_Error: %s\n",string); + + if (sv.active) + Host_ShutdownServer (false); + + if (cls.state == ca_dedicated) + Sys_Error ("Host_Error: %s\n",string); // dedicated servers exit + + CL_Disconnect (); + cls.demonum = -1; + cl.intermission = 0; //johnfitz -- for errors during intermissions (changelevel with no map found, etc.) + + inerror = false; + + longjmp (host_abortserver, 1); +} + +/* +================ +Host_FindMaxClients +================ +*/ +void Host_FindMaxClients (void) +{ + int i; + + svs.maxclients = 1; + + i = COM_CheckParm ("-dedicated"); + if (i) + { + cls.state = ca_dedicated; + if (i != (com_argc - 1)) + { + svs.maxclients = Q_atoi (com_argv[i+1]); + } + else + svs.maxclients = 8; + } + else + cls.state = ca_disconnected; + + i = COM_CheckParm ("-listen"); + if (i) + { + if (cls.state == ca_dedicated) + Sys_Error ("Only one of -dedicated or -listen can be specified"); + if (i != (com_argc - 1)) + svs.maxclients = Q_atoi (com_argv[i+1]); + else + svs.maxclients = 8; + } + if (svs.maxclients < 1) + svs.maxclients = 8; + else if (svs.maxclients > MAX_SCOREBOARD) + svs.maxclients = MAX_SCOREBOARD; + + svs.maxclientslimit = svs.maxclients; + if (svs.maxclientslimit < 4) + svs.maxclientslimit = 4; + svs.clients = (struct client_s *) Hunk_AllocName (svs.maxclientslimit*sizeof(client_t), "clients"); + + if (svs.maxclients > 1) + Cvar_SetQuick (&deathmatch, "1"); + else + Cvar_SetQuick (&deathmatch, "0"); +} + +void Host_Version_f (void) +{ + Con_Printf ("Quake Version %1.2f\n", VERSION); + Con_Printf ("QuakeSpasm Version " QUAKESPASM_VER_STRING "\n"); + Con_Printf ("Exe: " __TIME__ " " __DATE__ "\n"); +} + +/* cvar callback functions : */ +void Host_Callback_Notify (cvar_t *var) +{ + if (sv.active) + SV_BroadcastPrintf ("\"%s\" changed to \"%s\"\n", var->name, var->string); +} + +/* +======================= +Host_InitLocal +====================== +*/ +void Host_InitLocal (void) +{ + Cmd_AddCommand ("version", Host_Version_f); + + Host_InitCommands (); + + Cvar_RegisterVariable (&host_framerate); + Cvar_RegisterVariable (&host_speeds); + Cvar_RegisterVariable (&host_maxfps); //johnfitz + Cvar_SetCallback (&host_maxfps, Max_Fps_f); + Cvar_RegisterVariable (&host_timescale); //johnfitz + + Cvar_RegisterVariable (&max_edicts); //johnfitz + Cvar_SetCallback (&max_edicts, Max_Edicts_f); + Cvar_RegisterVariable (&devstats); //johnfitz + + Cvar_RegisterVariable (&sys_ticrate); + Cvar_RegisterVariable (&sys_throttle); + Cvar_RegisterVariable (&serverprofile); + + Cvar_RegisterVariable (&fraglimit); + Cvar_RegisterVariable (&timelimit); + Cvar_RegisterVariable (&teamplay); + Cvar_SetCallback (&fraglimit, Host_Callback_Notify); + Cvar_SetCallback (&timelimit, Host_Callback_Notify); + Cvar_SetCallback (&teamplay, Host_Callback_Notify); + Cvar_RegisterVariable (&samelevel); + Cvar_RegisterVariable (&noexit); + Cvar_SetCallback (&noexit, Host_Callback_Notify); + Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&developer); + Cvar_RegisterVariable (&coop); + Cvar_RegisterVariable (&deathmatch); + + Cvar_RegisterVariable (&campaign); + Cvar_RegisterVariable (&horde); + Cvar_RegisterVariable (&sv_cheats); + + Cvar_RegisterVariable (&pausable); + + Cvar_RegisterVariable (&temp1); + + Host_FindMaxClients (); +} + + +/* +=============== +Host_WriteConfiguration + +Writes key bindings and archived cvars to config.cfg +=============== +*/ +void Host_WriteConfiguration (void) +{ + FILE *f; + +// dedicated servers initialize the host but don't parse and set the +// config.cfg cvars + if (host_initialized && !isDedicated && !host_parms->errstate) + { + f = fopen (va("%s/config.cfg", com_gamedir), "w"); + if (!f) + { + Con_Printf ("Couldn't write config.cfg.\n"); + return; + } + + //VID_SyncCvars (); //johnfitz -- write actual current mode to config file, in case cvars were messed with + + Key_WriteBindings (f); + Cvar_WriteVariables (f); + + //johnfitz -- extra commands to preserve state + fprintf (f, "vid_restart\n"); + if (in_mlook.state & 1) fprintf (f, "+mlook\n"); + //johnfitz + + fclose (f); + } +} + + +/* +================= +SV_ClientPrintf + +Sends text across to be displayed +FIXME: make this just a stuffed echo? +================= +*/ +void SV_ClientPrintf (const char *fmt, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,fmt); + q_vsnprintf (string, sizeof(string), fmt,argptr); + va_end (argptr); + + MSG_WriteByte (&host_client->message, svc_print); + MSG_WriteString (&host_client->message, string); +} + +/* +================= +SV_BroadcastPrintf + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrintf (const char *fmt, ...) +{ + va_list argptr; + char string[1024]; + int i; + + va_start (argptr,fmt); + q_vsnprintf (string, sizeof(string), fmt, argptr); + va_end (argptr); + + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].active && svs.clients[i].spawned) + { + MSG_WriteByte (&svs.clients[i].message, svc_print); + MSG_WriteString (&svs.clients[i].message, string); + } + } +} + +/* +================= +Host_ClientCommands + +Send text over to the client to be executed +================= +*/ +void Host_ClientCommands (const char *fmt, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,fmt); + q_vsnprintf (string, sizeof(string), fmt, argptr); + va_end (argptr); + + MSG_WriteByte (&host_client->message, svc_stufftext); + MSG_WriteString (&host_client->message, string); +} + +/* +===================== +SV_DropClient + +Called when the player is getting totally kicked off the host +if (crash = true), don't bother sending signofs +===================== +*/ +void SV_DropClient (qboolean crash) +{ + int saveSelf; + int i; + client_t *client; + + if (!crash) + { + // send any final messages (don't check for errors) + if (NET_CanSendMessage (host_client->netconnection)) + { + MSG_WriteByte (&host_client->message, svc_disconnect); + NET_SendMessage (host_client->netconnection, &host_client->message); + } + + if (host_client->edict && host_client->spawned) + { + // call the prog function for removing a client + // this will set the body to a dead frame, among other things + saveSelf = pr_global_struct->self; + pr_global_struct->self = EDICT_TO_PROG(host_client->edict); + PR_ExecuteProgram (pr_global_struct->ClientDisconnect); + pr_global_struct->self = saveSelf; + } + + Sys_Printf ("Client %s removed\n",host_client->name); + } + +// break the net connection + NET_Close (host_client->netconnection); + host_client->netconnection = NULL; + +// free the client (the body stays around) + host_client->active = false; + host_client->name[0] = 0; + host_client->old_frags = -999999; + net_activeconnections--; + +// send notification to all clients + for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) + { + if (!client->active) + continue; + MSG_WriteByte (&client->message, svc_updatename); + MSG_WriteByte (&client->message, host_client - svs.clients); + MSG_WriteString (&client->message, ""); + MSG_WriteByte (&client->message, svc_updatefrags); + MSG_WriteByte (&client->message, host_client - svs.clients); + MSG_WriteShort (&client->message, 0); + MSG_WriteByte (&client->message, svc_updatecolors); + MSG_WriteByte (&client->message, host_client - svs.clients); + MSG_WriteByte (&client->message, 0); + } +} + +/* +================== +Host_ShutdownServer + +This only happens at the end of a game, not between levels +================== +*/ +void Host_ShutdownServer(qboolean crash) +{ + int i; + int count; + sizebuf_t buf; + byte message[4]; + double start; + + if (!sv.active) + return; + + sv.active = false; + +// stop all client sounds immediately + if (cls.state == ca_connected) + CL_Disconnect (); + +// flush any pending messages - like the score!!! + start = Sys_DoubleTime(); + do + { + count = 0; + for (i=0, host_client = svs.clients ; iactive && host_client->message.cursize) + { + if (NET_CanSendMessage (host_client->netconnection)) + { + NET_SendMessage(host_client->netconnection, &host_client->message); + SZ_Clear (&host_client->message); + } + else + { + NET_GetMessage(host_client->netconnection); + count++; + } + } + } + if ((Sys_DoubleTime() - start) > 3.0) + break; + } + while (count); + +// make sure all the clients know we're disconnecting + buf.data = message; + buf.maxsize = 4; + buf.cursize = 0; + MSG_WriteByte(&buf, svc_disconnect); + count = NET_SendToAll(&buf, 5.0); + if (count) + Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count); + + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + if (host_client->active) + SV_DropClient(crash); + +// +// clear structures +// +// memset (&sv, 0, sizeof(sv)); // ServerSpawn already do this by Host_ClearMemory + memset (svs.clients, 0, svs.maxclientslimit*sizeof(client_t)); +} + + +/* +================ +Host_ClearMemory + +This clears all the memory used by both the client and server, but does +not reinitialize anything. +================ +*/ +void Host_ClearMemory (void) +{ + Con_DPrintf ("Clearing memory\n"); + D_FlushCaches (); + Mod_ClearAll (); + Sky_ClearAll(); +/* host_hunklevel MUST be set at this point */ + Hunk_FreeToLowMark (host_hunklevel); + cls.signon = 0; // not CL_ClearSignons() + free(sv.edicts); // ericw -- sv.edicts switched to use malloc() + memset (&sv, 0, sizeof(sv)); + memset (&cl, 0, sizeof(cl)); +} + + +//============================================================================== +// +// Host Frame +// +//============================================================================== + +/* +=================== +Host_FilterTime + +Returns false if the time is too short to run a frame +=================== +*/ +qboolean Host_FilterTime (float time) +{ + float maxfps; //johnfitz + + realtime += time; + + //johnfitz -- max fps cvar + maxfps = CLAMP (10.f, host_maxfps.value, 1000.f); + if (!cls.timedemo && realtime - oldrealtime < 1.0/maxfps) + return false; // framerate is too high + //johnfitz + + host_frametime = realtime - oldrealtime; + oldrealtime = realtime; + + //johnfitz -- host_timescale is more intuitive than host_framerate + if (host_timescale.value > 0) + host_frametime *= host_timescale.value; + //johnfitz + else if (host_framerate.value > 0) + host_frametime = host_framerate.value; + else // don't allow really long or short frames + host_frametime = CLAMP (0.001, host_frametime, 0.1); //johnfitz -- use CLAMP + + return true; +} + +/* +=================== +Host_GetConsoleCommands + +Add them exactly as if they had been typed at the console +=================== +*/ +void Host_GetConsoleCommands (void) +{ + const char *cmd; + + if (!isDedicated) + return; // no stdin necessary in graphical mode + + while (1) + { + cmd = Sys_ConsoleInput (); + if (!cmd) + break; + Cbuf_AddText (cmd); + } +} + +/* +================== +Host_ServerFrame +================== +*/ +void Host_ServerFrame (void) +{ + int i, active; //johnfitz + edict_t *ent; //johnfitz + +// run the world state + pr_global_struct->frametime = host_frametime; + +// set the time and clear the general datagram + SV_ClearDatagram (); + +// check for new clients + SV_CheckForNewClients (); + +// read client messages + SV_RunClients (); + +// move things around and think +// always pause in single player if in console or menus + if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) ) + SV_Physics (); + +//johnfitz -- devstats + if (cls.signon == SIGNONS) + { + for (i=0, active=0; ifree) + active++; + } + if (active > 600 && dev_peakstats.edicts <= 600) + Con_DWarning ("%i edicts exceeds standard limit of 600 (max = %d).\n", active, sv.max_edicts); + dev_stats.edicts = active; + dev_peakstats.edicts = q_max(active, dev_peakstats.edicts); + } +//johnfitz + +// send all messages to the clients + SV_SendClientMessages (); +} + +/* +================== +Host_Frame + +Runs all active servers +================== +*/ +void _Host_Frame (float time) +{ + static double time1 = 0; + static double time2 = 0; + static double time3 = 0; + int pass1, pass2, pass3; + + if (setjmp (host_abortserver) ) + return; // something bad happened, or the server disconnected + +// keep the random time dependent + rand (); + +// decide the simulation time + if (!Host_FilterTime (time)) + return; // don't run too fast, or packets will flood out + +// get new key events + Key_UpdateForDest (); + IN_UpdateInputMode (); + Sys_SendKeyEvents (); + +// allow mice or other external controllers to add commands + IN_Commands (); + +// process console commands + Cbuf_Execute (); + + NET_Poll(); + +// if running the server locally, make intentions now + if (sv.active) + CL_SendCmd (); + +//------------------- +// +// server operations +// +//------------------- + +// check for commands typed to the host + Host_GetConsoleCommands (); + + if (sv.active) + Host_ServerFrame (); + +//------------------- +// +// client operations +// +//------------------- + +// if running the server remotely, send intentions now after +// the incoming messages have been read + if (!sv.active) + CL_SendCmd (); + +// fetch results from server + if (cls.state == ca_connected) + CL_ReadFromServer (); + +// update video + if (host_speeds.value) + time1 = Sys_DoubleTime (); + + SCR_UpdateScreen (); + + CL_RunParticles (); //johnfitz -- seperated from rendering + + if (host_speeds.value) + time2 = Sys_DoubleTime (); + +// update audio + BGM_Update(); // adds music raw samples and/or advances midi driver + if (cls.signon == SIGNONS) + { + S_Update (r_origin, vpn, vright, vup); + CL_DecayLights (); + } + else + S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin); + + CDAudio_Update(); + + if (host_speeds.value) + { + pass1 = (time1 - time3)*1000; + time3 = Sys_DoubleTime (); + pass2 = (time2 - time1)*1000; + pass3 = (time3 - time2)*1000; + Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n", + pass1+pass2+pass3, pass1, pass2, pass3); + } + + host_framecount++; + +} + +void Host_Frame (float time) +{ + double time1, time2; + static double timetotal; + static int timecount; + int i, c, m; + + if (!serverprofile.value) + { + _Host_Frame (time); + return; + } + + time1 = Sys_DoubleTime (); + _Host_Frame (time); + time2 = Sys_DoubleTime (); + + timetotal += time2 - time1; + timecount++; + + if (timecount < 1000) + return; + + m = timetotal*1000/timecount; + timecount = 0; + timetotal = 0; + c = 0; + for (i = 0; i < svs.maxclients; i++) + { + if (svs.clients[i].active) + c++; + } + + Con_Printf ("serverprofile: %2i clients %2i msec\n", c, m); +} + +/* +==================== +Host_Init +==================== +*/ +void Host_Init (void) +{ + if (standard_quake) + minimum_memory = MINIMUM_MEMORY; + else minimum_memory = MINIMUM_MEMORY_LEVELPAK; + + if (COM_CheckParm ("-minmemory")) + host_parms->memsize = minimum_memory; + + if (host_parms->memsize < minimum_memory) + Sys_Error ("Only %4.1f megs of memory available, can't execute game", host_parms->memsize / (float)0x100000); + + com_argc = host_parms->argc; + com_argv = host_parms->argv; + + Memory_Init (host_parms->membase, host_parms->memsize); + Cbuf_Init (); + Cmd_Init (); + LOG_Init (host_parms); + Cvar_Init (); //johnfitz + COM_Init (); + COM_InitFilesystem (); + Host_InitLocal (); + W_LoadWadFile (); //johnfitz -- filename is now hard-coded for honesty + if (cls.state != ca_dedicated) + { + Key_Init (); + Con_Init (); + } + PR_Init (); + Mod_Init (); + NET_Init (); + SV_Init (); + + Con_Printf ("Exe: " __TIME__ " " __DATE__ "\n"); + Con_Printf ("%4.1f megabyte heap\n", host_parms->memsize/ (1024*1024.0)); + + if (cls.state != ca_dedicated) + { + host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp", NULL); + if (!host_colormap) + Sys_Error ("Couldn't load gfx/colormap.lmp"); + + V_Init (); + Chase_Init (); + M_Init (); + ExtraMaps_Init (); //johnfitz + Modlist_Init (); //johnfitz + DemoList_Init (); //ericw + VID_Init (); + IN_Init (); + TexMgr_Init (); //johnfitz + Draw_Init (); + SCR_Init (); + R_Init (); + S_Init (); + CDAudio_Init (); + BGM_Init(); + Sbar_Init (); + CL_Init (); + } + + LOC_Init (); // for 2021 rerelease support. + + Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); + host_hunklevel = Hunk_LowMark (); + + host_initialized = true; + Con_Printf ("\n========= Quake Initialized =========\n\n"); + + if (cls.state != ca_dedicated) + { + Cbuf_InsertText ("exec quake.rc\n"); + // johnfitz -- in case the vid mode was locked during vid_init, we can unlock it now. + // note: two leading newlines because the command buffer swallows one of them. + Cbuf_AddText ("\n\nvid_unlock\n"); + } + + if (cls.state == ca_dedicated) + { + Cbuf_AddText ("exec autoexec.cfg\n"); + Cbuf_AddText ("stuffcmds"); + Cbuf_Execute (); + if (!sv.active) + Cbuf_AddText ("map start\n"); + } +} + + +/* +=============== +Host_Shutdown + +FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better +to run quit through here before the final handoff to the sys code. +=============== +*/ +void Host_Shutdown(void) +{ + static qboolean isdown = false; + + if (isdown) + { + printf ("recursive shutdown\n"); + return; + } + isdown = true; + +// keep Con_Printf from trying to update the screen + scr_disabled_for_loading = true; + + Host_WriteConfiguration (); + + NET_Shutdown (); + + if (cls.state != ca_dedicated) + { + if (con_initialized) + History_Shutdown (); + BGM_Shutdown(); + CDAudio_Shutdown (); + S_Shutdown (); + IN_Shutdown (); + VID_Shutdown(); + } + + LOG_Close (); + + LOC_Shutdown (); +} diff --git a/Quake/host_cmd.cpp b/Quake/host_cmd.cpp new file mode 100644 index 0000000..f1f3013 --- /dev/null +++ b/Quake/host_cmd.cpp @@ -0,0 +1,2364 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" +#ifndef _WIN32 +#include +#endif + +extern cvar_t pausable; + +int current_skill; + +/* +================== +Host_Quit_f +================== +*/ +void Host_Quit_f (void) +{ + if (key_dest != key_console && cls.state != ca_dedicated) + { + M_Menu_Quit_f (); + return; + } + CL_Disconnect (); + Host_ShutdownServer(false); + + Sys_Quit (); +} + +//============================================================================== +//johnfitz -- extramaps management +//============================================================================== + +/* +================== +FileList_Add +================== +*/ +static void FileList_Add (const char *name, filelist_item_t **list) +{ + filelist_item_t *item,*cursor,*prev; + + // ignore duplicate + for (item = *list; item; item = item->next) + { + if (!Q_strcmp (name, item->name)) + return; + } + + item = (filelist_item_t *) Z_Malloc(sizeof(filelist_item_t)); + q_strlcpy (item->name, name, sizeof(item->name)); + + // insert each entry in alphabetical order + if (*list == NULL || + q_strcasecmp(item->name, (*list)->name) < 0) //insert at front + { + item->next = *list; + *list = item; + } + else //insert later + { + prev = *list; + cursor = (*list)->next; + while (cursor && (q_strcasecmp(item->name, cursor->name) > 0)) + { + prev = cursor; + cursor = cursor->next; + } + item->next = prev->next; + prev->next = item; + } +} + +static void FileList_Clear (filelist_item_t **list) +{ + filelist_item_t *blah; + + while (*list) + { + blah = (*list)->next; + Z_Free(*list); + *list = blah; + } +} + +filelist_item_t *extralevels; + +static void ExtraMaps_Add (const char *name) +{ + FileList_Add(name, &extralevels); +} + +void ExtraMaps_Init (void) +{ +#ifdef _WIN32 + WIN32_FIND_DATA fdat; + HANDLE fhnd; +#else + DIR *dir_p; + struct dirent *dir_t; +#endif + char filestring[MAX_OSPATH]; + char mapname[32]; + char ignorepakdir[32]; + searchpath_t *search; + pack_t *pak; + int i; + + // we don't want to list the maps in id1 pakfiles, + // because these are not "add-on" levels + q_snprintf (ignorepakdir, sizeof(ignorepakdir), "/%s/", GAMENAME); + + for (search = com_searchpaths; search; search = search->next) + { + if (*search->filename) //directory + { +#ifdef _WIN32 + q_snprintf (filestring, sizeof(filestring), "%s/maps/*.bsp", search->filename); + fhnd = FindFirstFile(filestring, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + continue; + do + { + COM_StripExtension(fdat.cFileName, mapname, sizeof(mapname)); + ExtraMaps_Add (mapname); + } while (FindNextFile(fhnd, &fdat)); + FindClose(fhnd); +#else + q_snprintf (filestring, sizeof(filestring), "%s/maps/", search->filename); + dir_p = opendir(filestring); + if (dir_p == NULL) + continue; + while ((dir_t = readdir(dir_p)) != NULL) + { + if (q_strcasecmp(COM_FileGetExtension(dir_t->d_name), "bsp") != 0) + continue; + COM_StripExtension(dir_t->d_name, mapname, sizeof(mapname)); + ExtraMaps_Add (mapname); + } + closedir(dir_p); +#endif + } + else //pakfile + { + if (!strstr(search->pack->filename, ignorepakdir)) + { //don't list standard id maps + for (i = 0, pak = search->pack; i < pak->numfiles; i++) + { + if (!strcmp(COM_FileGetExtension(pak->files[i].name), "bsp")) + { + if (pak->files[i].filelen > 32*1024) + { // don't list files under 32k (ammo boxes etc) + COM_StripExtension(pak->files[i].name + 5, mapname, sizeof(mapname)); + ExtraMaps_Add (mapname); + } + } + } + } + } + } +} + +static void ExtraMaps_Clear (void) +{ + FileList_Clear(&extralevels); +} + +void ExtraMaps_NewGame (void) +{ + ExtraMaps_Clear (); + ExtraMaps_Init (); +} + +/* +================== +Host_Maps_f +================== +*/ +static void Host_Maps_f (void) +{ + int i; + filelist_item_t *level; + + for (level = extralevels, i = 0; level; level = level->next, i++) + Con_SafePrintf (" %s\n", level->name); + + if (i) + Con_SafePrintf ("%i map(s)\n", i); + else + Con_SafePrintf ("no maps found\n"); +} + +//============================================================================== +//johnfitz -- modlist management +//============================================================================== + +filelist_item_t *modlist; + +static void Modlist_Add (const char *name) +{ + FileList_Add(name, &modlist); +} + +#ifdef _WIN32 +void Modlist_Init (void) +{ + WIN32_FIND_DATA fdat; + HANDLE fhnd; + DWORD attribs; + char dir_string[MAX_OSPATH], mod_string[MAX_OSPATH]; + + q_snprintf (dir_string, sizeof(dir_string), "%s/*", com_basedir); + fhnd = FindFirstFile(dir_string, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + return; + + do + { + if (!strcmp(fdat.cFileName, ".") || !strcmp(fdat.cFileName, "..")) + continue; + q_snprintf (mod_string, sizeof(mod_string), "%s/%s", com_basedir, fdat.cFileName); + attribs = GetFileAttributes (mod_string); + if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) { + /* don't bother testing for pak files / progs.dat */ + Modlist_Add(fdat.cFileName); + } + } while (FindNextFile(fhnd, &fdat)); + + FindClose(fhnd); +} +#else +void Modlist_Init (void) +{ + DIR *dir_p, *mod_dir_p; + struct dirent *dir_t; + char dir_string[MAX_OSPATH], mod_string[MAX_OSPATH]; + + q_snprintf (dir_string, sizeof(dir_string), "%s/", com_basedir); + dir_p = opendir(dir_string); + if (dir_p == NULL) + return; + + while ((dir_t = readdir(dir_p)) != NULL) + { + if (!strcmp(dir_t->d_name, ".") || !strcmp(dir_t->d_name, "..")) + continue; + if (!q_strcasecmp (COM_FileGetExtension (dir_t->d_name), "app")) // skip .app bundles on macOS + continue; + q_snprintf(mod_string, sizeof(mod_string), "%s%s/", dir_string, dir_t->d_name); + mod_dir_p = opendir(mod_string); + if (mod_dir_p == NULL) + continue; + /* don't bother testing for pak files / progs.dat */ + Modlist_Add(dir_t->d_name); + closedir(mod_dir_p); + } + + closedir(dir_p); +} +#endif + +//============================================================================== +//ericw -- demo list management +//============================================================================== + +filelist_item_t *demolist; + +static void DemoList_Clear (void) +{ + FileList_Clear (&demolist); +} + +void DemoList_Rebuild (void) +{ + DemoList_Clear (); + DemoList_Init (); +} + +// TODO: Factor out to a general-purpose file searching function +void DemoList_Init (void) +{ +#ifdef _WIN32 + WIN32_FIND_DATA fdat; + HANDLE fhnd; +#else + DIR *dir_p; + struct dirent *dir_t; +#endif + char filestring[MAX_OSPATH]; + char demname[32]; + char ignorepakdir[32]; + searchpath_t *search; + pack_t *pak; + int i; + + // we don't want to list the demos in id1 pakfiles, + // because these are not "add-on" demos + q_snprintf (ignorepakdir, sizeof(ignorepakdir), "/%s/", GAMENAME); + + for (search = com_searchpaths; search; search = search->next) + { + if (*search->filename) //directory + { +#ifdef _WIN32 + q_snprintf (filestring, sizeof(filestring), "%s/*.dem", search->filename); + fhnd = FindFirstFile(filestring, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + continue; + do + { + COM_StripExtension(fdat.cFileName, demname, sizeof(demname)); + FileList_Add (demname, &demolist); + } while (FindNextFile(fhnd, &fdat)); + FindClose(fhnd); +#else + q_snprintf (filestring, sizeof(filestring), "%s/", search->filename); + dir_p = opendir(filestring); + if (dir_p == NULL) + continue; + while ((dir_t = readdir(dir_p)) != NULL) + { + if (q_strcasecmp(COM_FileGetExtension(dir_t->d_name), "dem") != 0) + continue; + COM_StripExtension(dir_t->d_name, demname, sizeof(demname)); + FileList_Add (demname, &demolist); + } + closedir(dir_p); +#endif + } + else //pakfile + { + if (!strstr(search->pack->filename, ignorepakdir)) + { //don't list standard id demos + for (i = 0, pak = search->pack; i < pak->numfiles; i++) + { + if (!strcmp(COM_FileGetExtension(pak->files[i].name), "dem")) + { + COM_StripExtension(pak->files[i].name, demname, sizeof(demname)); + FileList_Add (demname, &demolist); + } + } + } + } + } +} + +/* +================== +Host_Mods_f -- johnfitz + +list all potential mod directories (contain either a pak file or a progs.dat) +================== +*/ +static void Host_Mods_f (void) +{ + int i; + filelist_item_t *mod; + + for (mod = modlist, i=0; mod; mod = mod->next, i++) + Con_SafePrintf (" %s\n", mod->name); + + if (i) + Con_SafePrintf ("%i mod(s)\n", i); + else + Con_SafePrintf ("no mods found\n"); +} + +//============================================================================== + +/* +============= +Host_Mapname_f -- johnfitz +============= +*/ +static void Host_Mapname_f (void) +{ + if (sv.active) + { + Con_Printf ("\"mapname\" is \"%s\"\n", sv.name); + return; + } + + if (cls.state == ca_connected) + { + Con_Printf ("\"mapname\" is \"%s\"\n", cl.mapname); + return; + } + + Con_Printf ("no map loaded\n"); +} + +/* +================== +Host_Status_f +================== +*/ +static void Host_Status_f (void) +{ + void (*print_fn) (const char *fmt, ...) + FUNCP_PRINTF(1,2); + client_t *client; + int seconds; + int minutes; + int hours = 0; + int j; + + if (cmd_source == src_command) + { + if (!sv.active) + { + Cmd_ForwardToServer (); + return; + } + print_fn = Con_Printf; + } + else + print_fn = SV_ClientPrintf; + + print_fn ("host: %s\n", Cvar_VariableString ("hostname")); + print_fn ("version: %4.2f\n", VERSION); + if (tcpipAvailable) + print_fn ("tcp/ip: %s\n", my_tcpip_address); + if (ipxAvailable) + print_fn ("ipx: %s\n", my_ipx_address); + print_fn ("map: %s\n", sv.name); + print_fn ("players: %i active (%i max)\n\n", net_activeconnections, svs.maxclients); + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (!client->active) + continue; + seconds = (int)(net_time - NET_QSocketGetTime(client->netconnection)); + minutes = seconds / 60; + if (minutes) + { + seconds -= (minutes * 60); + hours = minutes / 60; + if (hours) + minutes -= (hours * 60); + } + else + hours = 0; + print_fn ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", j+1, client->name, (int)client->edict->v.frags, hours, minutes, seconds); + print_fn (" %s\n", NET_QSocketGetAddressString(client->netconnection)); + } +} + +/* +================== +Host_God_f + +Sets client to godmode +================== +*/ +static void Host_God_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set god mode to on or off + switch (Cmd_Argc()) + { + case 1: + sv_player->v.flags = (int)sv_player->v.flags ^ FL_GODMODE; + if (!((int)sv_player->v.flags & FL_GODMODE) ) + SV_ClientPrintf ("godmode OFF\n"); + else + SV_ClientPrintf ("godmode ON\n"); + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + sv_player->v.flags = (int)sv_player->v.flags | FL_GODMODE; + SV_ClientPrintf ("godmode ON\n"); + } + else + { + sv_player->v.flags = (int)sv_player->v.flags & ~FL_GODMODE; + SV_ClientPrintf ("godmode OFF\n"); + } + break; + default: + Con_Printf("god [value] : toggle god mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +static void Host_Buddha_f (void) { + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + sv_player->v.flags = (int)sv_player->v.flags ^ FL_BUDDHAMODE; + if (!((int)sv_player->v.flags & FL_BUDDHAMODE) ) + SV_ClientPrintf ("buddha mode OFF\n"); + else + SV_ClientPrintf ("buddha mode ON\n"); +} + +/* +================== +Host_Notarget_f +================== +*/ +static void Host_Notarget_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set notarget to on or off + switch (Cmd_Argc()) + { + case 1: + sv_player->v.flags = (int)sv_player->v.flags ^ FL_NOTARGET; + if (!((int)sv_player->v.flags & FL_NOTARGET) ) + SV_ClientPrintf ("notarget OFF\n"); + else + SV_ClientPrintf ("notarget ON\n"); + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + sv_player->v.flags = (int)sv_player->v.flags | FL_NOTARGET; + SV_ClientPrintf ("notarget ON\n"); + } + else + { + sv_player->v.flags = (int)sv_player->v.flags & ~FL_NOTARGET; + SV_ClientPrintf ("notarget OFF\n"); + } + break; + default: + Con_Printf("notarget [value] : toggle notarget mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +qboolean noclip_anglehack; + +/* +================== +Host_Noclip_f +================== +*/ +static void Host_Noclip_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set noclip to on or off + switch (Cmd_Argc()) + { + case 1: + if (sv_player->v.movetype != MOVETYPE_NOCLIP) + { + noclip_anglehack = true; + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf ("noclip ON\n"); + } + else + { + noclip_anglehack = false; + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("noclip OFF\n"); + } + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + noclip_anglehack = true; + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf ("noclip ON\n"); + } + else + { + noclip_anglehack = false; + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("noclip OFF\n"); + } + break; + default: + Con_Printf("noclip [value] : toggle noclip mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +/* +==================== +Host_SetPos_f + +adapted from fteqw, originally by Alex Shadowalker +==================== +*/ +static void Host_SetPos_f(void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + if (Cmd_Argc() != 7 && Cmd_Argc() != 4) + { + SV_ClientPrintf("usage:\n"); + SV_ClientPrintf(" setpos \n"); + SV_ClientPrintf(" setpos \n"); + SV_ClientPrintf("current values:\n"); + SV_ClientPrintf(" %i %i %i %i %i %i\n", + (int)sv_player->v.origin[0], + (int)sv_player->v.origin[1], + (int)sv_player->v.origin[2], + (int)sv_player->v.v_angle[0], + (int)sv_player->v.v_angle[1], + (int)sv_player->v.v_angle[2]); + return; + } + + if (sv_player->v.movetype != MOVETYPE_NOCLIP) + { + noclip_anglehack = true; + sv_player->v.movetype = MOVETYPE_NOCLIP; + SV_ClientPrintf ("noclip ON\n"); + } + + //make sure they're not going to whizz away from it + sv_player->v.velocity[0] = 0; + sv_player->v.velocity[1] = 0; + sv_player->v.velocity[2] = 0; + + sv_player->v.origin[0] = atof(Cmd_Argv(1)); + sv_player->v.origin[1] = atof(Cmd_Argv(2)); + sv_player->v.origin[2] = atof(Cmd_Argv(3)); + + if (Cmd_Argc() == 7) + { + sv_player->v.angles[0] = atof(Cmd_Argv(4)); + sv_player->v.angles[1] = atof(Cmd_Argv(5)); + sv_player->v.angles[2] = atof(Cmd_Argv(6)); + sv_player->v.fixangle = 1; + } + + SV_LinkEdict (sv_player, false); +} + +/* +================== +Host_Fly_f + +Sets client to flymode +================== +*/ +static void Host_Fly_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + //johnfitz -- allow user to explicitly set noclip to on or off + switch (Cmd_Argc()) + { + case 1: + if (sv_player->v.movetype != MOVETYPE_FLY) + { + sv_player->v.movetype = MOVETYPE_FLY; + SV_ClientPrintf ("flymode ON\n"); + } + else + { + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("flymode OFF\n"); + } + break; + case 2: + if (Q_atof(Cmd_Argv(1))) + { + sv_player->v.movetype = MOVETYPE_FLY; + SV_ClientPrintf ("flymode ON\n"); + } + else + { + sv_player->v.movetype = MOVETYPE_WALK; + SV_ClientPrintf ("flymode OFF\n"); + } + break; + default: + Con_Printf("fly [value] : toggle fly mode. values: 0 = off, 1 = on\n"); + break; + } + //johnfitz +} + +/* +================== +Host_Ping_f + +================== +*/ +static void Host_Ping_f (void) +{ + int i, j; + float total; + client_t *client; + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + SV_ClientPrintf ("Client ping times:\n"); + for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) + { + if (!client->active) + continue; + total = 0; + for (j = 0; j < NUM_PING_TIMES; j++) + total+=client->ping_times[j]; + total /= NUM_PING_TIMES; + SV_ClientPrintf ("%4i %s\n", (int)(total*1000), client->name); + } +} + +/* +=============================================================================== + +SERVER TRANSITIONS + +=============================================================================== +*/ + +/* +====================== +Host_Map_f + +handle a +map +command from the console. Active clients are kicked off. +====================== +*/ +static void Host_Map_f (void) +{ + int i; + char name[MAX_QPATH], *p; + + if (Cmd_Argc() < 2) //no map name given + { + if (cls.state == ca_dedicated) + { + if (sv.active) + Con_Printf ("Current map: %s\n", sv.name); + else + Con_Printf ("Server not active\n"); + } + else if (cls.state == ca_connected) + { + Con_Printf ("Current map: %s ( %s )\n", cl.levelname, cl.mapname); + } + else + { + Con_Printf ("map : start a new server\n"); + } + return; + } + + if (cmd_source != src_command) + return; + + cls.demonum = -1; // stop demo loop in case this fails + + CL_Disconnect (); + Host_ShutdownServer(false); + + if (cls.state != ca_dedicated) + IN_Activate(); + key_dest = key_game; // remove console or menu + SCR_BeginLoadingPlaque (); + + svs.serverflags = 0; // haven't completed an episode yet + q_strlcpy (name, Cmd_Argv(1), sizeof(name)); + // remove (any) trailing ".bsp" from mapname -- S.A. + p = strstr(name, ".bsp"); + if (p && p[4] == '\0') + *p = '\0'; + SV_SpawnServer (name); + if (!sv.active) + return; + + if (cls.state != ca_dedicated) + { + memset (cls.spawnparms, 0, MAX_MAPSTRING); + for (i = 2; i < Cmd_Argc(); i++) + { + q_strlcat (cls.spawnparms, Cmd_Argv(i), MAX_MAPSTRING); + q_strlcat (cls.spawnparms, " ", MAX_MAPSTRING); + } + + Cmd_ExecuteString ("connect local", src_command); + } +} + +/* +====================== +Host_Randmap_f + +Loads a random map from the "maps" list. +====================== +*/ +static void Host_Randmap_f (void) +{ + int i, randlevel, numlevels; + filelist_item_t *level; + + if (cmd_source != src_command) + return; + + for (level = extralevels, numlevels = 0; level; level = level->next) + numlevels++; + + if (numlevels == 0) + { + Con_Printf ("no maps\n"); + return; + } + + randlevel = (rand() % numlevels); + + for (level = extralevels, i = 0; level; level = level->next, i++) + { + if (i == randlevel) + { + Con_Printf ("Starting map %s...\n", level->name); + Cbuf_AddText (va("map %s\n", level->name)); + return; + } + } +} + +/* +================== +Host_Changelevel_f + +Goes to a new map, taking all clients along +================== +*/ +static void Host_Changelevel_f (void) +{ + char level[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Con_Printf ("changelevel : continue game on a new level\n"); + return; + } + if (!sv.active || cls.demoplayback) + { + Con_Printf ("Only the server may changelevel\n"); + return; + } + + //johnfitz -- check for client having map before anything else + q_snprintf (level, sizeof(level), "maps/%s.bsp", Cmd_Argv(1)); + if (!COM_FileExists(level, NULL)) + Host_Error ("cannot find map %s", level); + //johnfitz + + if (cls.state != ca_dedicated) + IN_Activate(); // -- S.A. + key_dest = key_game; // remove console or menu + SV_SaveSpawnparms (); + q_strlcpy (level, Cmd_Argv(1), sizeof(level)); + SV_SpawnServer (level); + // also issue an error if spawn failed -- O.S. + if (!sv.active) + Host_Error ("cannot run map %s", level); +} + +/* +================== +Host_Restart_f + +Restarts the current server for a dead player +================== +*/ +static void Host_Restart_f (void) +{ + char mapname[MAX_QPATH]; + + if (cls.demoplayback || !sv.active) + return; + + if (cmd_source != src_command) + return; + q_strlcpy (mapname, sv.name, sizeof(mapname)); // mapname gets cleared in spawnserver + SV_SpawnServer (mapname); + if (!sv.active) + Host_Error ("cannot restart map %s", mapname); +} + +/* +================== +Host_Reconnect_f + +This command causes the client to wait for the signon messages again. +This is sent just before a server changes levels +================== +*/ +static void Host_Reconnect_f (void) +{ + if (cls.demoplayback) // cross-map demo playback fix from Baker + return; + + SCR_BeginLoadingPlaque (); + CL_ClearSignons (); // need new connection messages +} + +/* +===================== +Host_Connect_f + +User command to connect to server +===================== +*/ +static void Host_Connect_f (void) +{ + char name[MAX_QPATH]; + + cls.demonum = -1; // stop demo loop in case this fails + if (cls.demoplayback) + { + CL_StopPlayback (); + CL_Disconnect (); + } + q_strlcpy (name, Cmd_Argv(1), sizeof(name)); + CL_EstablishConnection (name); + Host_Reconnect_f (); +} + + +/* +=============================================================================== + +LOAD / SAVE GAME + +=============================================================================== +*/ + +#define SAVEGAME_VERSION 5 + +/* +=============== +Host_SavegameComment + +Writes a SAVEGAME_COMMENT_LENGTH character comment describing the current +=============== +*/ +static void Host_SavegameComment (char text[SAVEGAME_COMMENT_LENGTH + 1]) +{ + int i; + char kills[20]; + char *p; + + for (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++) + text[i] = ' '; + text[SAVEGAME_COMMENT_LENGTH] = '\0'; + + i = (int) strlen(cl.levelname); + if (i > 22) i = 22; + memcpy (text, cl.levelname, (size_t)i); + +// Remove CR/LFs from level name to avoid broken saves, e.g. with autumn_sp map: +// https://celephais.net/board/view_thread.php?id=60452&start=3666 + while ((p = strchr(text, '\n')) != NULL) + *p = ' '; + while ((p = strchr(text, '\r')) != NULL) + *p = ' '; + + sprintf (kills,"kills:%3i/%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + memcpy (text+22, kills, strlen(kills)); + +// convert space to _ to make stdio happy + for (i = 0; i < SAVEGAME_COMMENT_LENGTH; i++) + { + if (text[i] == ' ') + text[i] = '_'; + } +} + +/* +=============== +Host_Savegame_f +=============== +*/ +static void Host_Savegame_f (void) +{ + char name[MAX_OSPATH]; + FILE *f; + int i; + char comment[SAVEGAME_COMMENT_LENGTH+1]; + + if (cmd_source != src_command) + return; + + if (!sv.active) + { + Con_Printf ("Not playing a local game.\n"); + return; + } + + if (cl.intermission) + { + Con_Printf ("Can't save in intermission.\n"); + return; + } + + if (svs.maxclients != 1) + { + Con_Printf ("Can't save multiplayer games.\n"); + return; + } + + if (Cmd_Argc() != 2) + { + Con_Printf ("save : save a game\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Printf ("Relative pathnames are not allowed.\n"); + return; + } + + for (i=0 ; iv.health <= 0) ) + { + Con_Printf ("Can't savegame with a dead player\n"); + return; + } + } + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1)); + COM_AddExtension (name, ".sav", sizeof(name)); + + Con_Printf ("Saving game to %s...\n", name); + f = fopen (name, "w"); + if (!f) + { + Con_Printf ("ERROR: couldn't open.\n"); + return; + } + + fprintf (f, "%i\n", SAVEGAME_VERSION); + Host_SavegameComment (comment); + fprintf (f, "%s\n", comment); + for (i = 0; i < NUM_SPAWN_PARMS; i++) + fprintf (f, "%f\n", svs.clients->spawn_parms[i]); + fprintf (f, "%d\n", current_skill); + fprintf (f, "%s\n", sv.name); + fprintf (f, "%f\n",sv.time); + +// write the light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + if (sv.lightstyles[i]) + fprintf (f, "%s\n", sv.lightstyles[i]); + else + fprintf (f,"m\n"); + } + + ED_WriteGlobals (f); + for (i = 0; i < sv.num_edicts; i++) + { + ED_Write (f, EDICT_NUM(i)); + fflush (f); + } + fclose (f); + Con_Printf ("done.\n"); +} + +/* +=============== +Host_Loadgame_f +=============== +*/ +static void Host_Loadgame_f (void) +{ + static char *start; + + char name[MAX_OSPATH]; + char mapname[MAX_QPATH]; + float time, tfloat; + const char *data; + int i; + edict_t *ent; + int entnum; + int version; + float spawn_parms[NUM_SPAWN_PARMS]; + + if (cmd_source != src_command) + return; + + if (Cmd_Argc() != 2) + { + Con_Printf ("load : load a game\n"); + return; + } + + if (strstr(Cmd_Argv(1), "..")) + { + Con_Printf ("Relative pathnames are not allowed.\n"); + return; + } + + cls.demonum = -1; // stop demo loop in case this fails + + q_snprintf (name, sizeof(name), "%s/%s", com_gamedir, Cmd_Argv(1)); + COM_AddExtension (name, ".sav", sizeof(name)); + +// we can't call SCR_BeginLoadingPlaque, because too much stack space has +// been used. The menu calls it before stuffing loadgame command +// SCR_BeginLoadingPlaque (); + + Con_Printf ("Loading game from %s...\n", name); + +// avoid leaking if the previous Host_Loadgame_f failed with a Host_Error + if (start != NULL) + free (start); + + start = (char *) COM_LoadMallocFile_TextMode_OSPath(name, NULL); + if (start == NULL) + { + Con_Printf ("ERROR: couldn't open.\n"); + return; + } + + data = start; + data = COM_ParseIntNewline (data, &version); + if (version != SAVEGAME_VERSION) + { + free (start); + start = NULL; + Host_Error ("Savegame is version %i, not %i", version, SAVEGAME_VERSION); + return; + } + data = COM_ParseStringNewline (data); + for (i = 0; i < NUM_SPAWN_PARMS; i++) + data = COM_ParseFloatNewline (data, &spawn_parms[i]); +// this silliness is so we can load 1.06 save files, which have float skill values + data = COM_ParseFloatNewline(data, &tfloat); + current_skill = (int)(tfloat + 0.1); + Cvar_SetValue ("skill", (float)current_skill); + + data = COM_ParseStringNewline (data); + q_strlcpy (mapname, com_token, sizeof(mapname)); + data = COM_ParseFloatNewline (data, &time); + + CL_Disconnect_f (); + + SV_SpawnServer (mapname); + + if (!sv.active) + { + free (start); + start = NULL; + SCR_EndLoadingPlaque (); + Con_Printf ("Couldn't load map\n"); + return; + } + sv.paused = true; // pause until all clients connect + sv.loadgame = true; + +// load the light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + data = COM_ParseStringNewline (data); + sv.lightstyles[i] = (const char *)Hunk_Strdup (com_token, "lightstyles"); + } + +// load the edicts out of the savegame file + entnum = -1; // -1 is the globals + while (*data) + { + data = COM_Parse (data); + if (!com_token[0]) + break; // end of file + if (strcmp(com_token,"{")) + { + Host_Error ("First token isn't a brace"); + } + + if (entnum == -1) + { // parse the global vars + data = ED_ParseGlobals (data); + } + else + { // parse an edict + ent = EDICT_NUM(entnum); + if (entnum < sv.num_edicts) { + ent->free = false; + memset (&ent->v, 0, progs->entityfields * 4); + } + else { + memset (ent, 0, pr_edict_size); + ent->baseline.scale = ENTSCALE_DEFAULT; + } + data = ED_ParseEdict (data, ent); + + // link it into the bsp tree + if (!ent->free) + SV_LinkEdict (ent, false); + } + + entnum++; + } + + // Free edicts allocated during map loading but no longer used after restoring saved game state + for (i = entnum; i < sv.num_edicts; i++) + ED_Free(EDICT_NUM(i)); + + sv.num_edicts = entnum; + sv.time = time; + + free (start); + start = NULL; + + for (i = 0; i < NUM_SPAWN_PARMS; i++) + svs.clients->spawn_parms[i] = spawn_parms[i]; + + if (cls.state != ca_dedicated) + { + CL_EstablishConnection ("local"); + Host_Reconnect_f (); + } + + if (cls.state != ca_dedicated) + IN_Activate(); // moved to here from M_Load_Key() +} + +//============================================================================ + +/* +====================== +Host_Name_f +====================== +*/ +static void Host_Name_f (void) +{ + char newName[32]; + + if (Cmd_Argc () == 1) + { + Con_Printf ("\"name\" is \"%s\"\n", cl_name.string); + return; + } + if (Cmd_Argc () == 2) + q_strlcpy(newName, Cmd_Argv(1), sizeof(newName)); + else + q_strlcpy(newName, Cmd_Args(), sizeof(newName)); + newName[15] = 0; // client_t structure actually says name[32]. + + if (cmd_source == src_command) + { + if (Q_strcmp(cl_name.string, newName) == 0) + return; + Cvar_Set ("_cl_name", newName); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + return; + } + + if (host_client->name[0] && strcmp(host_client->name, "unconnected") ) + { + if (Q_strcmp(host_client->name, newName) != 0) + Con_Printf ("%s renamed to %s\n", host_client->name, newName); + } + Q_strcpy (host_client->name, newName); + host_client->edict->v.netname = PR_SetEngineString(host_client->name); + +// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatename); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteString (&sv.reliable_datagram, host_client->name); +} + +static void Host_Say(qboolean teamonly) +{ + int j; + client_t *client; + client_t *save; + const char *p; + char text[MAXCMDLINE], *p2; + qboolean quoted; + qboolean fromServer = false; + + if (cmd_source == src_command) + { + if (cls.state != ca_dedicated) + { + Cmd_ForwardToServer (); + return; + } + fromServer = true; + teamonly = false; + } + + if (Cmd_Argc () < 2) + return; + + save = host_client; + + p = Cmd_Args(); +// remove quotes if present + quoted = false; + if (*p == '\"') + { + p++; + quoted = true; + } +// turn on color set 1 + if (!fromServer) + q_snprintf (text, sizeof(text), "\001%s: %s", save->name, p); + else + q_snprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p); + +// check length & truncate if necessary + j = (int) strlen(text); + if (j >= (int) sizeof(text) - 1) + { + text[sizeof(text) - 2] = '\n'; + text[sizeof(text) - 1] = '\0'; + } + else + { + p2 = text + j; + while ((const char *)p2 > (const char *)text && + (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)) ) + { + if (p2[-1] == '\"' && quoted) + quoted = false; + p2[-1] = '\0'; + p2--; + } + p2[0] = '\n'; + p2[1] = '\0'; + } + + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (!client || !client->active || !client->spawned) + continue; + if (teamplay.value && teamonly && client->edict->v.team != save->edict->v.team) + continue; + host_client = client; + SV_ClientPrintf("%s", text); + } + host_client = save; + + if (cls.state == ca_dedicated) + Sys_Printf("%s", &text[1]); +} + +static void Host_Say_f(void) +{ + Host_Say(false); +} + +static void Host_Say_Team_f(void) +{ + Host_Say(true); +} + +static void Host_Tell_f(void) +{ + int j; + client_t *client; + client_t *save; + const char *p; + char text[MAXCMDLINE], *p2; + qboolean quoted; + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (Cmd_Argc () < 3) + return; + + p = Cmd_Args(); +// remove quotes if present + quoted = false; + if (*p == '\"') + { + p++; + quoted = true; + } + q_snprintf (text, sizeof(text), "%s: %s", host_client->name, p); + +// check length & truncate if necessary + j = (int) strlen(text); + if (j >= (int) sizeof(text) - 1) + { + text[sizeof(text) - 2] = '\n'; + text[sizeof(text) - 1] = '\0'; + } + else + { + p2 = text + j; + while ((const char *)p2 > (const char *)text && + (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)) ) + { + if (p2[-1] == '\"' && quoted) + quoted = false; + p2[-1] = '\0'; + p2--; + } + p2[0] = '\n'; + p2[1] = '\0'; + } + + save = host_client; + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (!client->active || !client->spawned) + continue; + if (q_strcasecmp(client->name, Cmd_Argv(1))) + continue; + host_client = client; + SV_ClientPrintf("%s", text); + break; + } + host_client = save; +} + +/* +================== +Host_Color_f +================== +*/ +static void Host_Color_f(void) +{ + int top, bottom; + int playercolor; + + if (Cmd_Argc() == 1) + { + Con_Printf ("\"color\" is \"%i %i\"\n", ((int)cl_color.value) >> 4, ((int)cl_color.value) & 0x0f); + Con_Printf ("color <0-13> [0-13]\n"); + return; + } + + if (Cmd_Argc() == 2) + top = bottom = atoi(Cmd_Argv(1)); + else + { + top = atoi(Cmd_Argv(1)); + bottom = atoi(Cmd_Argv(2)); + } + + top &= 15; + if (top > 13) + top = 13; + bottom &= 15; + if (bottom > 13) + bottom = 13; + + playercolor = top*16 + bottom; + + if (cmd_source == src_command) + { + Cvar_SetValue ("_cl_color", playercolor); + if (cls.state == ca_connected) + Cmd_ForwardToServer (); + return; + } + + host_client->colors = playercolor; + host_client->edict->v.team = bottom + 1; + +// send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); + MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); + MSG_WriteByte (&sv.reliable_datagram, host_client->colors); +} + +/* +================== +Host_Kill_f +================== +*/ +static void Host_Kill_f (void) +{ + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (sv_player->v.health <= 0) + { + SV_ClientPrintf ("Can't suicide -- already dead!\n"); + return; + } + + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(sv_player); + PR_ExecuteProgram (pr_global_struct->ClientKill); +} + +/* +================== +Host_Pause_f +================== +*/ +static void Host_Pause_f (void) +{ +//ericw -- demo pause support (inspired by MarkV) + if (cls.demoplayback) + { + cls.demopaused = !cls.demopaused; + cl.paused = cls.demopaused; + return; + } + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + if (!pausable.value) + SV_ClientPrintf ("Pause not allowed.\n"); + else + { + sv.paused ^= 1; + + if (sv.paused) + { + SV_BroadcastPrintf ("%s paused the game\n", PR_GetString(sv_player->v.netname)); + } + else + { + SV_BroadcastPrintf ("%s unpaused the game\n",PR_GetString(sv_player->v.netname)); + } + + // send notification to all clients + MSG_WriteByte (&sv.reliable_datagram, svc_setpause); + MSG_WriteByte (&sv.reliable_datagram, sv.paused); + } +} + +//=========================================================================== + +/* +================== +Host_PreSpawn_f +================== +*/ +static void Host_PreSpawn_f (void) +{ + if (cmd_source == src_command) + { + Con_Printf ("prespawn is not valid from the console\n"); + return; + } + + if (host_client->spawned) + { + Con_Printf ("prespawn not valid -- already spawned\n"); + return; + } + + host_client->sendsignon = PRESPAWN_SIGNONBUFS; + host_client->signonidx = 0; +} + +/* +================== +Host_Spawn_f +================== +*/ +static void Host_Spawn_f (void) +{ + int i; + client_t *client; + edict_t *ent; + + if (cmd_source == src_command) + { + Con_Printf ("spawn is not valid from the console\n"); + return; + } + + if (host_client->spawned) + { + Con_Printf ("Spawn not valid -- already spawned\n"); + return; + } + +// run the entrance script + if (sv.loadgame) + { // loaded games are fully inited already + // if this is the last client to be connected, unpause + sv.paused = false; + } + else + { + // set up the edict + ent = host_client->edict; + + memset (&ent->v, 0, progs->entityfields * 4); + ent->v.colormap = NUM_FOR_EDICT(ent); + ent->v.team = (host_client->colors & 15) + 1; + ent->v.netname = PR_SetEngineString(host_client->name); + + // copy spawn parms out of the client_t + for (i=0 ; i< NUM_SPAWN_PARMS ; i++) + (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i]; + // call the spawn function + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(sv_player); + PR_ExecuteProgram (pr_global_struct->ClientConnect); + + if ((Sys_DoubleTime() - NET_QSocketGetTime(host_client->netconnection)) <= sv.time) + Sys_Printf ("%s entered the game\n", host_client->name); + + PR_ExecuteProgram (pr_global_struct->PutClientInServer); + } + +// send all current names, colors, and frag counts + SZ_Clear (&host_client->message); + +// send time of update + MSG_WriteByte (&host_client->message, svc_time); + MSG_WriteFloat (&host_client->message, sv.time); + + for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) + { + MSG_WriteByte (&host_client->message, svc_updatename); + MSG_WriteByte (&host_client->message, i); + MSG_WriteString (&host_client->message, client->name); + MSG_WriteByte (&host_client->message, svc_updatefrags); + MSG_WriteByte (&host_client->message, i); + MSG_WriteShort (&host_client->message, client->old_frags); + MSG_WriteByte (&host_client->message, svc_updatecolors); + MSG_WriteByte (&host_client->message, i); + MSG_WriteByte (&host_client->message, client->colors); + } + +// send all current light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) + { + MSG_WriteByte (&host_client->message, svc_lightstyle); + MSG_WriteByte (&host_client->message, (char)i); + MSG_WriteString (&host_client->message, sv.lightstyles[i]); + } + +// +// send some stats +// + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_TOTALSECRETS); + MSG_WriteLong (&host_client->message, pr_global_struct->total_secrets); + + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_TOTALMONSTERS); + MSG_WriteLong (&host_client->message, pr_global_struct->total_monsters); + + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_SECRETS); + MSG_WriteLong (&host_client->message, pr_global_struct->found_secrets); + + MSG_WriteByte (&host_client->message, svc_updatestat); + MSG_WriteByte (&host_client->message, STAT_MONSTERS); + MSG_WriteLong (&host_client->message, pr_global_struct->killed_monsters); + +// +// send a fixangle +// Never send a roll angle, because savegames can catch the server +// in a state where it is expecting the client to correct the angle +// and it won't happen if the game was just loaded, so you wind up +// with a permanent head tilt + ent = EDICT_NUM( 1 + (host_client - svs.clients) ); + MSG_WriteByte (&host_client->message, svc_setangle); + for (i = 0; i < 2; i++) + MSG_WriteAngle (&host_client->message, ent->v.angles[i], sv.protocolflags ); + MSG_WriteAngle (&host_client->message, 0, sv.protocolflags ); + + SV_WriteClientdataToMessage (sv_player, &host_client->message); + + MSG_WriteByte (&host_client->message, svc_signonnum); + MSG_WriteByte (&host_client->message, 3); + host_client->sendsignon = PRESPAWN_FLUSH; +} + +/* +================== +Host_Begin_f +================== +*/ +static void Host_Begin_f (void) +{ + if (cmd_source == src_command) + { + Con_Printf ("begin is not valid from the console\n"); + return; + } + + host_client->spawned = true; +} + +//=========================================================================== + +/* +================== +Host_Kick_f + +Kicks a user off of the server +================== +*/ +static void Host_Kick_f (void) +{ + const char *who; + const char *message = NULL; + client_t *save; + int i; + qboolean byNumber = false; + + if (cmd_source == src_command) + { + if (!sv.active) + { + Cmd_ForwardToServer (); + return; + } + } + else if (pr_global_struct->deathmatch) + return; + + save = host_client; + + if (Cmd_Argc() > 2 && Q_strcmp(Cmd_Argv(1), "#") == 0) + { + i = Q_atof(Cmd_Argv(2)) - 1; + if (i < 0 || i >= svs.maxclients) + return; + if (!svs.clients[i].active) + return; + host_client = &svs.clients[i]; + byNumber = true; + } + else + { + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + { + if (!host_client->active) + continue; + if (q_strcasecmp(host_client->name, Cmd_Argv(1)) == 0) + break; + } + } + + if (i < svs.maxclients) + { + if (cmd_source == src_command) + if (cls.state == ca_dedicated) + who = "Console"; + else + who = cl_name.string; + else + who = save->name; + + // can't kick yourself! + if (host_client == save) + return; + + if (Cmd_Argc() > 2) + { + message = COM_Parse(Cmd_Args()); + if (byNumber) + { + message++; // skip the # + while (*message == ' ') // skip white space + message++; + message += strlen(Cmd_Argv(2)); // skip the number + } + while (*message && *message == ' ') + message++; + } + if (message) + SV_ClientPrintf ("Kicked by %s: %s\n", who, message); + else + SV_ClientPrintf ("Kicked by %s\n", who); + SV_DropClient (false); + } + + host_client = save; +} + +/* +=============================================================================== + +DEBUGGING TOOLS + +=============================================================================== +*/ + +/* +================== +Host_Give_f +================== +*/ +static void Host_Give_f (void) +{ + const char *t; + int v; + eval_t *val; + + if (cmd_source == src_command) + { + Cmd_ForwardToServer (); + return; + } + + if (pr_global_struct->deathmatch) + return; + + t = Cmd_Argv(1); + v = atoi (Cmd_Argv(2)); + + switch (t[0]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // MED 01/04/97 added hipnotic give stuff + if (hipnotic) + { + if (t[0] == '6') + { + if (t[1] == 'a') + sv_player->v.items = (int)sv_player->v.items | HIT_PROXIMITY_GUN; + else + sv_player->v.items = (int)sv_player->v.items | IT_GRENADE_LAUNCHER; + } + else if (t[0] == '9') + sv_player->v.items = (int)sv_player->v.items | HIT_LASER_CANNON; + else if (t[0] == '0') + sv_player->v.items = (int)sv_player->v.items | HIT_MJOLNIR; + else if (t[0] >= '2') + sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); + } + else + { + if (t[0] >= '2') + sv_player->v.items = (int)sv_player->v.items | (IT_SHOTGUN << (t[0] - '2')); + } + break; + + case 's': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_shells1"); + if (val) + val->_float = v; + } + sv_player->v.ammo_shells = v; + break; + + case 'n': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_nails1"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_nails = v; + } + } + else + { + sv_player->v.ammo_nails = v; + } + break; + + case 'l': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_lava_nails"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_nails = v; + } + } + break; + + case 'r': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_rockets1"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_rockets = v; + } + } + else + { + sv_player->v.ammo_rockets = v; + } + break; + + case 'm': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_multi_rockets"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_rockets = v; + } + } + break; + + case 'h': + sv_player->v.health = v; + break; + + case 'c': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_cells1"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon <= IT_LIGHTNING) + sv_player->v.ammo_cells = v; + } + } + else + { + sv_player->v.ammo_cells = v; + } + break; + + case 'p': + if (rogue) + { + val = GetEdictFieldValue(sv_player, "ammo_plasma"); + if (val) + { + val->_float = v; + if (sv_player->v.weapon > IT_LIGHTNING) + sv_player->v.ammo_cells = v; + } + } + break; + + //johnfitz -- give armour + case 'a': + if (v > 150) + { + sv_player->v.armortype = 0.8; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - + ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + + IT_ARMOR3; + } + else if (v > 100) + { + sv_player->v.armortype = 0.6; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - + ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + + IT_ARMOR2; + } + else if (v >= 0) + { + sv_player->v.armortype = 0.3; + sv_player->v.armorvalue = v; + sv_player->v.items = sv_player->v.items - + ((int)(sv_player->v.items) & (int)(IT_ARMOR1 | IT_ARMOR2 | IT_ARMOR3)) + + IT_ARMOR1; + } + break; + //johnfitz + } + + //johnfitz -- update currentammo to match new ammo (so statusbar updates correctly) + switch ((int)(sv_player->v.weapon)) + { + case IT_SHOTGUN: + case IT_SUPER_SHOTGUN: + sv_player->v.currentammo = sv_player->v.ammo_shells; + break; + case IT_NAILGUN: + case IT_SUPER_NAILGUN: + case RIT_LAVA_SUPER_NAILGUN: + sv_player->v.currentammo = sv_player->v.ammo_nails; + break; + case IT_GRENADE_LAUNCHER: + case IT_ROCKET_LAUNCHER: + case RIT_MULTI_GRENADE: + case RIT_MULTI_ROCKET: + sv_player->v.currentammo = sv_player->v.ammo_rockets; + break; + case IT_LIGHTNING: + case HIT_LASER_CANNON: + case HIT_MJOLNIR: + sv_player->v.currentammo = sv_player->v.ammo_cells; + break; + case RIT_LAVA_NAILGUN: //same as IT_AXE + if (rogue) + sv_player->v.currentammo = sv_player->v.ammo_nails; + break; + case RIT_PLASMA_GUN: //same as HIT_PROXIMITY_GUN + if (rogue) + sv_player->v.currentammo = sv_player->v.ammo_cells; + if (hipnotic) + sv_player->v.currentammo = sv_player->v.ammo_rockets; + break; + } + //johnfitz +} + +static edict_t *FindViewthing (void) +{ + int i; + edict_t *e; + + for (i=0 ; iv.classname), "viewthing") ) + return e; + } + Con_Printf ("No viewthing on map\n"); + return NULL; +} + +/* +================== +Host_Viewmodel_f +================== +*/ +static void Host_Viewmodel_f (void) +{ + edict_t *e; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + + m = Mod_ForName (Cmd_Argv(1), false); + if (!m) + { + Con_Printf ("Can't load %s\n", Cmd_Argv(1)); + return; + } + + e->v.frame = 0; + cl.model_precache[(int)e->v.modelindex] = m; +} + +/* +================== +Host_Viewframe_f +================== +*/ +static void Host_Viewframe_f (void) +{ + edict_t *e; + int f; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + m = cl.model_precache[(int)e->v.modelindex]; + + f = atoi(Cmd_Argv(1)); + if (f >= m->numframes) + f = m->numframes - 1; + + e->v.frame = f; +} + +static void PrintFrameName (qmodel_t *m, int frame) +{ + aliashdr_t *hdr; + maliasframedesc_t *pframedesc; + + hdr = (aliashdr_t *)Mod_Extradata (m); + if (!hdr) + return; + pframedesc = &hdr->frames[frame]; + + Con_Printf ("frame %i: %s\n", frame, pframedesc->name); +} + +/* +================== +Host_Viewnext_f +================== +*/ +static void Host_Viewnext_f (void) +{ + edict_t *e; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + m = cl.model_precache[(int)e->v.modelindex]; + + e->v.frame = e->v.frame + 1; + if (e->v.frame >= m->numframes) + e->v.frame = m->numframes - 1; + + PrintFrameName (m, e->v.frame); +} + +/* +================== +Host_Viewprev_f +================== +*/ +static void Host_Viewprev_f (void) +{ + edict_t *e; + qmodel_t *m; + + e = FindViewthing (); + if (!e) + return; + + m = cl.model_precache[(int)e->v.modelindex]; + + e->v.frame = e->v.frame - 1; + if (e->v.frame < 0) + e->v.frame = 0; + + PrintFrameName (m, e->v.frame); +} + +/* +=============================================================================== + +DEMO LOOP CONTROL + +=============================================================================== +*/ + +/* +================== +Host_Startdemos_f +================== +*/ +static void Host_Startdemos_f (void) +{ + int i, c; + + if (cls.state == ca_dedicated) + return; + + c = Cmd_Argc() - 1; + if (c > MAX_DEMOS) + { + Con_Printf ("Max %i demos in demoloop\n", MAX_DEMOS); + c = MAX_DEMOS; + } + Con_Printf ("%i demo(s) in loop\n", c); + + for (i = 1; i < c + 1; i++) + q_strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof(cls.demos[0])); + + if (!sv.active && cls.demonum != -1 && !cls.demoplayback) + { + cls.demonum = 0; + if (!fitzmode && !cl_startdemos.value) + { /* QuakeSpasm customization: */ + /* go straight to menu, no CL_NextDemo */ + cls.demonum = -1; + Cbuf_InsertText("menu_main\n"); + return; + } + CL_NextDemo (); + } + else + { + cls.demonum = -1; + } +} + +/* +================== +Host_Demos_f + +Return to looping demos +================== +*/ +static void Host_Demos_f (void) +{ + if (cls.state == ca_dedicated) + return; + if (cls.demonum == -1) + cls.demonum = 1; + CL_Disconnect_f (); + CL_NextDemo (); +} + +/* +================== +Host_Stopdemo_f + +Return to looping demos +================== +*/ +static void Host_Stopdemo_f (void) +{ + if (cls.state == ca_dedicated) + return; + if (!cls.demoplayback) + return; + CL_StopPlayback (); + CL_Disconnect (); +} + +/* +================== +Host_Resetdemos + +Clear looping demo list (called on game change) +================== +*/ +void Host_Resetdemos (void) +{ + memset (cls.demos, 0, sizeof (cls.demos)); + cls.demonum = 0; +} + +//============================================================================= + +/* +================== +Host_InitCommands +================== +*/ +void Host_InitCommands (void) +{ + Cmd_AddCommand ("maps", Host_Maps_f); //johnfitz + Cmd_AddCommand ("mods", Host_Mods_f); //johnfitz + Cmd_AddCommand ("games", Host_Mods_f); // as an alias to "mods" -- S.A. / QuakeSpasm + Cmd_AddCommand ("mapname", Host_Mapname_f); //johnfitz + Cmd_AddCommand ("randmap", Host_Randmap_f); //ericw + + Cmd_AddCommand ("status", Host_Status_f); + Cmd_AddCommand ("quit", Host_Quit_f); + Cmd_AddCommand ("god", Host_God_f); + Cmd_AddCommand("buddha", Host_Buddha_f); + Cmd_AddCommand ("notarget", Host_Notarget_f); + Cmd_AddCommand ("fly", Host_Fly_f); + Cmd_AddCommand ("map", Host_Map_f); + Cmd_AddCommand ("restart", Host_Restart_f); + Cmd_AddCommand ("changelevel", Host_Changelevel_f); + Cmd_AddCommand ("connect", Host_Connect_f); + Cmd_AddCommand ("reconnect", Host_Reconnect_f); + Cmd_AddCommand ("name", Host_Name_f); + Cmd_AddCommand ("noclip", Host_Noclip_f); + Cmd_AddCommand ("setpos", Host_SetPos_f); //QuakeSpasm + + Cmd_AddCommand ("say", Host_Say_f); + Cmd_AddCommand ("say_team", Host_Say_Team_f); + Cmd_AddCommand ("tell", Host_Tell_f); + Cmd_AddCommand ("color", Host_Color_f); + Cmd_AddCommand ("kill", Host_Kill_f); + Cmd_AddCommand ("pause", Host_Pause_f); + Cmd_AddCommand ("spawn", Host_Spawn_f); + Cmd_AddCommand ("begin", Host_Begin_f); + Cmd_AddCommand ("prespawn", Host_PreSpawn_f); + Cmd_AddCommand ("kick", Host_Kick_f); + Cmd_AddCommand ("ping", Host_Ping_f); + Cmd_AddCommand ("load", Host_Loadgame_f); + Cmd_AddCommand ("save", Host_Savegame_f); + Cmd_AddCommand ("give", Host_Give_f); + + Cmd_AddCommand ("startdemos", Host_Startdemos_f); + Cmd_AddCommand ("demos", Host_Demos_f); + Cmd_AddCommand ("stopdemo", Host_Stopdemo_f); + + Cmd_AddCommand ("viewmodel", Host_Viewmodel_f); + Cmd_AddCommand ("viewframe", Host_Viewframe_f); + Cmd_AddCommand ("viewnext", Host_Viewnext_f); + Cmd_AddCommand ("viewprev", Host_Viewprev_f); +} + diff --git a/Quake/image.cpp b/Quake/image.cpp new file mode 100644 index 0000000..6369890 --- /dev/null +++ b/Quake/image.cpp @@ -0,0 +1,640 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +//image.c -- image loading + +#include "quakedef.hpp" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_WRITE_STATIC +#include "stb_image_write.hpp" + +#define LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_NO_COMPILE_CPP +#define LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_NO_COMPILE_ERROR_TEXT +#include "lodepng.hpp" +#include "lodepng.cpp" + +static char loadfilename[MAX_OSPATH]; //file scope so that error messages can use it + +typedef struct stdio_buffer_s { + FILE *f; + unsigned char buffer[1024]; + int size; + int pos; +} stdio_buffer_t; + +static stdio_buffer_t *Buf_Alloc(FILE *f) +{ + stdio_buffer_t *buf = (stdio_buffer_t *) calloc(1, sizeof(stdio_buffer_t)); + buf->f = f; + return buf; +} + +static void Buf_Free(stdio_buffer_t *buf) +{ + free(buf); +} + +static inline int Buf_GetC(stdio_buffer_t *buf) +{ + if (buf->pos >= buf->size) + { + buf->size = fread(buf->buffer, 1, sizeof(buf->buffer), buf->f); + buf->pos = 0; + + if (buf->size == 0) + return EOF; + } + + return buf->buffer[buf->pos++]; +} + +/* +============ +Image_LoadImage + +returns a pointer to hunk allocated RGBA data + +TODO: search order: tga png jpg pcx lmp +============ +*/ +byte *Image_LoadImage (const char *name, int *width, int *height) +{ + FILE *f; + + q_snprintf (loadfilename, sizeof(loadfilename), "%s.tga", name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadTGA (f, width, height); + + q_snprintf (loadfilename, sizeof(loadfilename), "%s.pcx", name); + COM_FOpenFile (loadfilename, &f, NULL); + if (f) + return Image_LoadPCX (f, width, height); + + return NULL; +} + +//============================================================================== +// +// TGA +// +//============================================================================== + +typedef struct targaheader_s { + 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_t; + +#define TARGAHEADERSIZE 18 /* size on disk */ + +int fgetLittleShort (FILE *f) +{ + byte b1, b2; + + b1 = fgetc(f); + b2 = fgetc(f); + + return (short)(b1 + b2*256); +} + +int fgetLittleLong (FILE *f) +{ + byte b1, b2, b3, b4; + + b1 = fgetc(f); + b2 = fgetc(f); + b3 = fgetc(f); + b4 = fgetc(f); + + return b1 + (b2<<8) + (b3<<16) + (b4<<24); +} + +/* +============ +Image_WriteTGA -- writes RGB or RGBA data to a TGA file + +returns true if successful + +TODO: support BGRA and BGR formats (since opengl can return them, and we don't have to swap) +============ +*/ +qboolean Image_WriteTGA (const char *name, byte *data, int width, int height, int bpp, qboolean upsidedown) +{ + int handle, i, size, temp, bytes; + char pathname[MAX_OSPATH]; + byte header[TARGAHEADERSIZE]; + + Sys_mkdir (com_gamedir); //if we've switched to a nonexistant gamedir, create it now so we don't crash + q_snprintf (pathname, sizeof(pathname), "%s/%s", com_gamedir, name); + handle = Sys_FileOpenWrite (pathname); + if (handle == -1) + return false; + + Q_memset (header, 0, TARGAHEADERSIZE); + header[2] = 2; // uncompressed type + header[12] = width&255; + header[13] = width>>8; + header[14] = height&255; + header[15] = height>>8; + header[16] = bpp; // pixel size + if (upsidedown) + header[17] = 0x20; //upside-down attribute + + // swap red and blue bytes + bytes = bpp/8; + size = width*height*bytes; + for (i=0; i 256) + Sys_Error ("Image_LoadTGA: %s has an %ibit palette", loadfilename, targa_header.colormap_type); + } + else + { + if (targa_header.image_type!=2 && targa_header.image_type!=10) + Sys_Error ("Image_LoadTGA: %s is not a type 2 or type 10 targa (%i)", loadfilename, targa_header.image_type); + + if (targa_header.colormap_type !=0 || (targa_header.pixel_size!=32 && targa_header.pixel_size!=24)) + Sys_Error ("Image_LoadTGA: %s is not a 24bit or 32bit targa", loadfilename); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + upside_down = !(targa_header.attributes & 0x20); //johnfitz -- fix for upside-down targas + + targa_rgba = (byte *) Hunk_Alloc (numPixels*4); + + if (targa_header.id_length != 0) + fseek(fin, targa_header.id_length, SEEK_CUR); // skip TARGA image comment + + buf = Buf_Alloc(fin); + + if (targa_header.image_type==1) // Uncompressed, paletted images + { + byte palette[256*4]; + int i; + //palette data comes first + for (i = 0; i < targa_header.colormap_length; i++) + { //this palette data is bgr. + palette[i*3+2] = Buf_GetC(buf); + palette[i*3+1] = Buf_GetC(buf); + palette[i*3+0] = Buf_GetC(buf); + palette[i*3+3] = 255; + } + for (i = targa_header.colormap_length*4; i < sizeof(palette); i++) + palette[i] = 0; + for(row=rows-1; row>=0; row--) + { + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + + for(column=0; column=0; row--) + { + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + for(column=0; column=0; row--) + { + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + for(column=0; column0) + row--; + else + goto breakOut; + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + } + } + } + else // non run-length packet + { + for(j=0;j0) + row--; + else + goto breakOut; + //johnfitz -- fix for upside-down targas + realrow = upside_down ? row : rows - 1 - row; + pixbuf = targa_rgba + realrow*columns*4; + //johnfitz + } + } + } + } + breakOut:; + } + } + + Buf_Free(buf); + fclose(fin); + + *width = (int)(targa_header.width); + *height = (int)(targa_header.height); + return targa_rgba; +} + +//============================================================================== +// +// PCX +// +//============================================================================== + +typedef struct +{ + char signature; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hdpi,vdpi; + byte colortable[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; +} pcxheader_t; + +/* +============ +Image_LoadPCX +============ +*/ +byte *Image_LoadPCX (FILE *f, int *width, int *height) +{ + pcxheader_t pcx; + int x, y, w, h, readbyte, runlength, start; + byte *p, *data; + byte palette[768]; + stdio_buffer_t *buf; + + start = ftell (f); //save start of file (since we might be inside a pak file, SEEK_SET might not be the start of the pcx) + + if (!fread(&pcx, sizeof(pcx), 1, f)) + Sys_Error ("Failed reading header from '%s'", loadfilename); + pcx.xmin = (unsigned short)LittleShort (pcx.xmin); + pcx.ymin = (unsigned short)LittleShort (pcx.ymin); + pcx.xmax = (unsigned short)LittleShort (pcx.xmax); + pcx.ymax = (unsigned short)LittleShort (pcx.ymax); + pcx.bytes_per_line = (unsigned short)LittleShort (pcx.bytes_per_line); + + if (pcx.signature != 0x0A) + Sys_Error ("'%s' is not a valid PCX file", loadfilename); + + if (pcx.version != 5) + Sys_Error ("'%s' is version %i, should be 5", loadfilename, pcx.version); + + if (pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcx.color_planes != 1) + Sys_Error ("'%s' has wrong encoding or bit depth", loadfilename); + + w = pcx.xmax - pcx.xmin + 1; + h = pcx.ymax - pcx.ymin + 1; + + data = (byte *) Hunk_Alloc((w*h+1)*4); //+1 to allow reading padding byte on last line + + //load palette + fseek (f, start + com_filesize - 768, SEEK_SET); + if (!fread (palette, 768, 1, f)) + Sys_Error ("Failed reading palette from '%s'", loadfilename); + + //back to start of image data + fseek (f, start + sizeof(pcx), SEEK_SET); + + buf = Buf_Alloc(f); + + for (y=0; y= 0xC0) + { + runlength = readbyte & 0x3F; + readbyte = Buf_GetC(buf); + } + else + runlength = 1; + + while(runlength--) + { + p[0] = palette[readbyte*3]; + p[1] = palette[readbyte*3+1]; + p[2] = palette[readbyte*3+2]; + p[3] = 255; + p += 4; + x++; + } + } + } + + Buf_Free(buf); + fclose(f); + + *width = w; + *height = h; + return data; +} + +//============================================================================== +// +// STB_IMAGE_WRITE +// +//============================================================================== + +static byte *CopyFlipped(const byte *data, int width, int height, int bpp) +{ + int y, rowsize; + byte *flipped; + + rowsize = width * (bpp / 8); + flipped = (byte *) malloc(height * rowsize); + if (!flipped) + return NULL; + + for (y=0; y +#else +#include +#endif +#else +#include "SDL.h" +#endif + +static qboolean windowhasfocus = true; //just in case sdl fails to tell us... +static qboolean textmode; + +static cvar_t in_debugkeys = {"in_debugkeys", "0", CVAR_NONE}; + +#ifdef __APPLE__ +/* Mouse acceleration needs to be disabled on OS X */ +#define MACOS_X_ACCELERATION_HACK +#endif + +#ifdef MACOS_X_ACCELERATION_HACK +#include +#include +#include +#include +#endif + +// SDL2 Game Controller cvars +cvar_t joy_deadzone_look = { "joy_deadzone_look", "0.175", CVAR_ARCHIVE }; +cvar_t joy_deadzone_move = { "joy_deadzone_move", "0.175", CVAR_ARCHIVE }; +cvar_t joy_outer_threshold_look = { "joy_outer_threshold_look", "0.02", CVAR_ARCHIVE }; +cvar_t joy_outer_threshold_move = { "joy_outer_threshold_move", "0.02", CVAR_ARCHIVE }; +cvar_t joy_deadzone_trigger = { "joy_deadzone_trigger", "0.2", CVAR_ARCHIVE }; +cvar_t joy_sensitivity_yaw = { "joy_sensitivity_yaw", "240", CVAR_ARCHIVE }; +cvar_t joy_sensitivity_pitch = { "joy_sensitivity_pitch", "130", CVAR_ARCHIVE }; +cvar_t joy_invert = { "joy_invert", "0", CVAR_ARCHIVE }; +cvar_t joy_exponent = { "joy_exponent", "2", CVAR_ARCHIVE }; +cvar_t joy_exponent_move = { "joy_exponent_move", "2", CVAR_ARCHIVE }; +cvar_t joy_swapmovelook = { "joy_swapmovelook", "0", CVAR_ARCHIVE }; +cvar_t joy_enable = { "joy_enable", "1", CVAR_ARCHIVE }; + +#if defined(USE_SDL2) +static SDL_JoystickID joy_active_instaceid = -1; +static SDL_GameController *joy_active_controller = NULL; +#endif + +static qboolean no_mouse = false; + +static int buttonremap[] = +{ + K_MOUSE1, + K_MOUSE3, /* right button */ + K_MOUSE2, /* middle button */ +#if !defined(USE_SDL2) /* mousewheel up/down not counted as buttons in SDL2 */ + K_MWHEELUP, + K_MWHEELDOWN, +#endif + K_MOUSE4, + K_MOUSE5 +}; + +/* total accumulated mouse movement since last frame */ +static int total_dx, total_dy = 0; + +static int SDLCALL IN_FilterMouseEvents (const SDL_Event *event) +{ + switch (event->type) + { + case SDL_MOUSEMOTION: + // case SDL_MOUSEBUTTONDOWN: + // case SDL_MOUSEBUTTONUP: + return 0; + } + + return 1; +} + +#if defined(USE_SDL2) +static int SDLCALL IN_SDL2_FilterMouseEvents (void *userdata, SDL_Event *event) +{ + return IN_FilterMouseEvents (event); +} +#endif + +static void IN_BeginIgnoringMouseEvents(void) +{ +#if defined(USE_SDL2) + SDL_EventFilter currentFilter = NULL; + void *currentUserdata = NULL; + SDL_GetEventFilter(¤tFilter, ¤tUserdata); + + if (currentFilter != IN_SDL2_FilterMouseEvents) + SDL_SetEventFilter(IN_SDL2_FilterMouseEvents, NULL); +#else + if (SDL_GetEventFilter() != IN_FilterMouseEvents) + SDL_SetEventFilter(IN_FilterMouseEvents); +#endif +} + +static void IN_EndIgnoringMouseEvents(void) +{ +#if defined(USE_SDL2) + SDL_EventFilter currentFilter; + void *currentUserdata; + if (SDL_GetEventFilter(¤tFilter, ¤tUserdata) == SDL_TRUE) + SDL_SetEventFilter(NULL, NULL); +#else + if (SDL_GetEventFilter() != NULL) + SDL_SetEventFilter(NULL); +#endif +} + +#ifdef MACOS_X_ACCELERATION_HACK +static cvar_t in_disablemacosxmouseaccel = {"in_disablemacosxmouseaccel", "1", CVAR_ARCHIVE}; +static double originalMouseSpeed = -1.0; + +static io_connect_t IN_GetIOHandle(void) +{ + io_connect_t iohandle = MACH_PORT_NULL; + io_service_t iohidsystem = MACH_PORT_NULL; + mach_port_t masterport; + kern_return_t status; + + status = IOMasterPort(MACH_PORT_NULL, &masterport); + if (status != KERN_SUCCESS) + return 0; + + iohidsystem = IORegistryEntryFromPath(masterport, kIOServicePlane ":/IOResources/IOHIDSystem"); + if (!iohidsystem) + return 0; + + status = IOServiceOpen(iohidsystem, mach_task_self(), kIOHIDParamConnectType, &iohandle); + IOObjectRelease(iohidsystem); + + return iohandle; +} + +static void IN_DisableOSXMouseAccel (void) +{ + io_connect_t mouseDev = IN_GetIOHandle(); + if (mouseDev != 0) + { + if (IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) + { + if (IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) + { + Cvar_Set("in_disablemacosxmouseaccel", "0"); + Con_Printf("WARNING: Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + } + } + else + { + Cvar_Set("in_disablemacosxmouseaccel", "0"); + Con_Printf("WARNING: Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); + } + IOServiceClose(mouseDev); + } + else + { + Cvar_Set("in_disablemacosxmouseaccel", "0"); + Con_Printf("WARNING: Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); + } +} + +static void IN_ReenableOSXMouseAccel (void) +{ + io_connect_t mouseDev = IN_GetIOHandle(); + if (mouseDev != 0) + { + if (IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) + Con_Printf("WARNING: Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); + IOServiceClose(mouseDev); + } + else + { + Con_Printf("WARNING: Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); + } + originalMouseSpeed = -1; +} +#endif /* MACOS_X_ACCELERATION_HACK */ + + +void IN_Activate (void) +{ + if (no_mouse) + return; + +#ifdef MACOS_X_ACCELERATION_HACK + /* Save the status of mouse acceleration */ + if (originalMouseSpeed == -1 && in_disablemacosxmouseaccel.value) + IN_DisableOSXMouseAccel(); +#endif + +#if defined(USE_SDL2) +#ifdef __APPLE__ + { + // Work around https://github.com/sezero/quakespasm/issues/48 + int width, height; + SDL_GetWindowSize((SDL_Window*) VID_GetWindow(), &width, &height); + SDL_WarpMouseInWindow((SDL_Window*) VID_GetWindow(), width / 2, height / 2); + } +#endif + + if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0) + { + Con_Printf("WARNING: SDL_SetRelativeMouseMode(SDL_TRUE) failed.\n"); + } +#else + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_ON) + { + SDL_WM_GrabInput(SDL_GRAB_ON); + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_ON) + Con_Printf("WARNING: SDL_WM_GrabInput(SDL_GRAB_ON) failed.\n"); + } + + if (SDL_ShowCursor(SDL_QUERY) != SDL_DISABLE) + { + SDL_ShowCursor(SDL_DISABLE); + if (SDL_ShowCursor(SDL_QUERY) != SDL_DISABLE) + Con_Printf("WARNING: SDL_ShowCursor(SDL_DISABLE) failed.\n"); + } +#endif + + IN_EndIgnoringMouseEvents(); + + total_dx = 0; + total_dy = 0; +} + +void IN_Deactivate (qboolean free_cursor) +{ + if (no_mouse) + return; + +#ifdef MACOS_X_ACCELERATION_HACK + if (originalMouseSpeed != -1) + IN_ReenableOSXMouseAccel(); +#endif + + if (free_cursor) + { +#if defined(USE_SDL2) + SDL_SetRelativeMouseMode(SDL_FALSE); +#else + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_OFF) + { + SDL_WM_GrabInput(SDL_GRAB_OFF); + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_OFF) + Con_Printf("WARNING: SDL_WM_GrabInput(SDL_GRAB_OFF) failed.\n"); + } + + if (SDL_ShowCursor(SDL_QUERY) != SDL_ENABLE) + { + SDL_ShowCursor(SDL_ENABLE); + if (SDL_ShowCursor(SDL_QUERY) != SDL_ENABLE) + Con_Printf("WARNING: SDL_ShowCursor(SDL_ENABLE) failed.\n"); + } +#endif + } + + /* discard all mouse events when input is deactivated */ + IN_BeginIgnoringMouseEvents(); +} + +void IN_StartupJoystick (void) +{ +#if defined(USE_SDL2) + int i; + int nummappings; + char controllerdb[MAX_OSPATH]; + SDL_GameController *gamecontroller; + + if (COM_CheckParm("-nojoy")) + return; + + if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) == -1 ) + { + Con_Warning("could not initialize SDL Game Controller\n"); + return; + } + + // Load additional SDL2 controller definitions from gamecontrollerdb.txt + q_snprintf (controllerdb, sizeof(controllerdb), "%s/gamecontrollerdb.txt", com_basedir); + nummappings = SDL_GameControllerAddMappingsFromFile(controllerdb); + if (nummappings > 0) + Con_Printf("%d mappings loaded from gamecontrollerdb.txt\n", nummappings); + + // Also try host_parms->userdir + if (host_parms->userdir != host_parms->basedir) + { + q_snprintf (controllerdb, sizeof(controllerdb), "%s/gamecontrollerdb.txt", host_parms->userdir); + nummappings = SDL_GameControllerAddMappingsFromFile(controllerdb); + if (nummappings > 0) + Con_Printf("%d mappings loaded from gamecontrollerdb.txt\n", nummappings); + } + + for (i = 0; i < SDL_NumJoysticks(); i++) + { + const char *joyname = SDL_JoystickNameForIndex(i); + if ( SDL_IsGameController(i) ) + { + const char *controllername = SDL_GameControllerNameForIndex(i); + gamecontroller = SDL_GameControllerOpen(i); + if (gamecontroller) + { + Con_Printf("detected controller: %s\n", controllername != NULL ? controllername : "NULL"); + + joy_active_instaceid = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamecontroller)); + joy_active_controller = gamecontroller; + break; + } + else + { + Con_Warning("failed to open controller: %s\n", controllername != NULL ? controllername : "NULL"); + } + } + else + { + Con_Warning("joystick missing controller mappings: %s\n", joyname != NULL ? joyname : "NULL" ); + } + } +#endif +} + +void IN_ShutdownJoystick (void) +{ +#if defined(USE_SDL2) + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); +#endif +} + +void IN_Init (void) +{ + textmode = Key_TextEntry(); + +#if !defined(USE_SDL2) + SDL_EnableUNICODE (textmode); + if (SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL) == -1) + Con_Printf("Warning: SDL_EnableKeyRepeat() failed.\n"); +#else + if (textmode) + SDL_StartTextInput(); + else + SDL_StopTextInput(); +#endif + if (safemode || COM_CheckParm("-nomouse")) + { + no_mouse = true; + /* discard all mouse events when input is deactivated */ + IN_BeginIgnoringMouseEvents(); + } + +#ifdef MACOS_X_ACCELERATION_HACK + Cvar_RegisterVariable(&in_disablemacosxmouseaccel); +#endif + Cvar_RegisterVariable(&in_debugkeys); + Cvar_RegisterVariable(&joy_sensitivity_yaw); + Cvar_RegisterVariable(&joy_sensitivity_pitch); + Cvar_RegisterVariable(&joy_deadzone_look); + Cvar_RegisterVariable(&joy_deadzone_move); + Cvar_RegisterVariable(&joy_outer_threshold_look); + Cvar_RegisterVariable(&joy_outer_threshold_move); + Cvar_RegisterVariable(&joy_deadzone_trigger); + Cvar_RegisterVariable(&joy_invert); + Cvar_RegisterVariable(&joy_exponent); + Cvar_RegisterVariable(&joy_exponent_move); + Cvar_RegisterVariable(&joy_swapmovelook); + Cvar_RegisterVariable(&joy_enable); + + IN_Activate(); + IN_StartupJoystick(); +} + +void IN_Shutdown (void) +{ + IN_Deactivate(true); + IN_ShutdownJoystick(); +} + +extern cvar_t cl_maxpitch; /* johnfitz -- variable pitch clamping */ +extern cvar_t cl_minpitch; /* johnfitz -- variable pitch clamping */ + + +void IN_MouseMotion(int dx, int dy) +{ + if (!windowhasfocus) + dx = dy = 0; //don't change view angles etc while unfocused. + total_dx += dx; + total_dy += dy; +} + +#if defined(USE_SDL2) +typedef struct joyaxis_s +{ + float x; + float y; +} joyaxis_t; + +typedef struct joy_buttonstate_s +{ + qboolean buttondown[SDL_CONTROLLER_BUTTON_MAX]; +} joybuttonstate_t; + +typedef struct axisstate_s +{ + float axisvalue[SDL_CONTROLLER_AXIS_MAX]; // normalized to +-1 +} joyaxisstate_t; + +static joybuttonstate_t joy_buttonstate; +static joyaxisstate_t joy_axisstate; + +static double joy_buttontimer[SDL_CONTROLLER_BUTTON_MAX]; +static double joy_emulatedkeytimer[6]; + +#ifdef __WATCOMC__ /* OW1.9 doesn't have powf() / sqrtf() */ +#define powf pow +#define sqrtf sqrt +#endif + +/* +================ +IN_AxisMagnitude + +Returns the vector length of the given joystick axis +================ +*/ +static vec_t IN_AxisMagnitude(joyaxis_t axis) +{ + vec_t magnitude = sqrtf((axis.x * axis.x) + (axis.y * axis.y)); + return magnitude; +} + +/* +================ +IN_ApplyEasing + +assumes axis values are in [-1, 1] and the vector magnitude has been clamped at 1. +Raises the axis values to the given exponent, keeping signs. +================ +*/ +static joyaxis_t IN_ApplyEasing(joyaxis_t axis, float exponent) +{ + joyaxis_t result = {0}; + vec_t eased_magnitude; + vec_t magnitude = IN_AxisMagnitude(axis); + + if (magnitude == 0) + return result; + + eased_magnitude = powf(magnitude, exponent); + + result.x = axis.x * (eased_magnitude / magnitude); + result.y = axis.y * (eased_magnitude / magnitude); + return result; +} + +/* +================ +IN_ApplyDeadzone + +in: raw joystick axis values converted to floats in +-1 +out: applies a circular inner deadzone and a circular outer threshold and clamps the magnitude at 1 + (my 360 controller is slightly non-circular and the stick travels further on the diagonals) + +deadzone is expected to satisfy 0 < deadzone < 1 - outer_threshold +outer_threshold is expected to satisfy 0 < outer_threshold < 1 - deadzone + +from https://github.com/jeremiah-sypult/Quakespasm-Rift +and adapted from http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html +================ +*/ +static joyaxis_t IN_ApplyDeadzone(joyaxis_t axis, float deadzone, float outer_threshold) +{ + joyaxis_t result = {0}; + vec_t magnitude = IN_AxisMagnitude(axis); + + if ( magnitude > deadzone ) { + // rescale the magnitude so deadzone becomes 0, and 1-outer_threshold becomes 1 + const vec_t new_magnitude = q_min(1.0, (magnitude - deadzone) / (1.0 - deadzone - outer_threshold)); + const vec_t scale = new_magnitude / magnitude; + result.x = axis.x * scale; + result.y = axis.y * scale; + } + + return result; +} + +/* +================ +IN_KeyForControllerButton +================ +*/ +static int IN_KeyForControllerButton(SDL_GameControllerButton button) +{ + switch (button) + { + case SDL_CONTROLLER_BUTTON_A: return K_ABUTTON; + case SDL_CONTROLLER_BUTTON_B: return K_BBUTTON; + case SDL_CONTROLLER_BUTTON_X: return K_XBUTTON; + case SDL_CONTROLLER_BUTTON_Y: return K_YBUTTON; + case SDL_CONTROLLER_BUTTON_BACK: return K_TAB; + case SDL_CONTROLLER_BUTTON_START: return K_ESCAPE; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: return K_LTHUMB; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return K_RTHUMB; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return K_LSHOULDER; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return K_RSHOULDER; + case SDL_CONTROLLER_BUTTON_DPAD_UP: return K_UPARROW; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return K_DOWNARROW; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return K_LEFTARROW; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return K_RIGHTARROW; + default: return 0; + } +} + +/* +================ +IN_JoyKeyEvent + +Sends a Key_Event if a unpressed -> pressed or pressed -> unpressed transition occurred, +and generates key repeats if the button is held down. + +Adapted from DarkPlaces by lordhavoc +================ +*/ +static void IN_JoyKeyEvent(qboolean wasdown, qboolean isdown, int key, double *timer) +{ + // we can't use `realtime` for key repeats because it is not monotomic + const double currenttime = Sys_DoubleTime(); + + if (wasdown) + { + if (isdown) + { + if (currenttime >= *timer) + { + *timer = currenttime + 0.1; + Key_Event(key, true); + } + } + else + { + *timer = 0; + Key_Event(key, false); + } + } + else + { + if (isdown) + { + *timer = currenttime + 0.5; + Key_Event(key, true); + } + } +} +#endif + +/* +================ +IN_Commands + +Emit key events for game controller buttons, including emulated buttons for analog sticks/triggers +================ +*/ +void IN_Commands (void) +{ +#if defined(USE_SDL2) + joyaxisstate_t newaxisstate; + int i; + const float stickthreshold = 0.9; + const float triggerthreshold = joy_deadzone_trigger.value; + + if (!joy_enable.value) + return; + + if (!joy_active_controller) + return; + + // emit key events for controller buttons + for (i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) + { + qboolean newstate = SDL_GameControllerGetButton(joy_active_controller, (SDL_GameControllerButton)i); + qboolean oldstate = joy_buttonstate.buttondown[i]; + + joy_buttonstate.buttondown[i] = newstate; + + // NOTE: This can cause a reentrant call of IN_Commands, via SCR_ModalMessage when confirming a new game. + IN_JoyKeyEvent(oldstate, newstate, IN_KeyForControllerButton((SDL_GameControllerButton)i), &joy_buttontimer[i]); + } + + for (i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) + { + newaxisstate.axisvalue[i] = SDL_GameControllerGetAxis(joy_active_controller, (SDL_GameControllerAxis)i) / 32768.0f; + } + + // emit emulated arrow keys so the analog sticks can be used in the menu + if (key_dest != key_game) + { + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] < -stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] < -stickthreshold, K_LEFTARROW, &joy_emulatedkeytimer[0]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] > stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX] > stickthreshold, K_RIGHTARROW, &joy_emulatedkeytimer[1]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] < -stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] < -stickthreshold, K_UPARROW, &joy_emulatedkeytimer[2]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] > stickthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY] > stickthreshold, K_DOWNARROW, &joy_emulatedkeytimer[3]); + } + + // emit emulated keys for the analog triggers + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERLEFT] > triggerthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERLEFT] > triggerthreshold, K_LTRIGGER, &joy_emulatedkeytimer[4]); + IN_JoyKeyEvent(joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] > triggerthreshold, newaxisstate.axisvalue[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] > triggerthreshold, K_RTRIGGER, &joy_emulatedkeytimer[5]); + + joy_axisstate = newaxisstate; +#endif +} + +/* +================ +IN_JoyMove +================ +*/ +void IN_JoyMove (usercmd_t *cmd) +{ +#if defined(USE_SDL2) + float speed; + joyaxis_t moveRaw, moveDeadzone, moveEased; + joyaxis_t lookRaw, lookDeadzone, lookEased; + extern cvar_t sv_maxspeed; + + if (!joy_enable.value) + return; + + if (!joy_active_controller) + return; + + if (cl.paused || key_dest != key_game) + return; + + moveRaw.x = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTX]; + moveRaw.y = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_LEFTY]; + lookRaw.x = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTX]; + lookRaw.y = joy_axisstate.axisvalue[SDL_CONTROLLER_AXIS_RIGHTY]; + + if (joy_swapmovelook.value) + { + joyaxis_t temp = moveRaw; + moveRaw = lookRaw; + lookRaw = temp; + } + + moveDeadzone = IN_ApplyDeadzone(moveRaw, joy_deadzone_move.value, joy_outer_threshold_move.value); + lookDeadzone = IN_ApplyDeadzone(lookRaw, joy_deadzone_look.value, joy_outer_threshold_look.value); + + moveEased = IN_ApplyEasing(moveDeadzone, joy_exponent_move.value); + lookEased = IN_ApplyEasing(lookDeadzone, joy_exponent.value); + + if ((in_speed.state & 1) ^ (cl_alwaysrun.value != 0.0 || cl_forwardspeed.value >= sv_maxspeed.value)) + // running + speed = sv_maxspeed.value; + else if (cl_forwardspeed.value >= sv_maxspeed.value) + // not running, with always run = vanilla + speed = q_min(sv_maxspeed.value, cl_forwardspeed.value / cl_movespeedkey.value); + else + // not running, with always run = off or quakespasm + speed = cl_forwardspeed.value; + + cmd->sidemove += speed * moveEased.x; + cmd->forwardmove -= speed * moveEased.y; + + cl.viewangles[YAW] -= lookEased.x * joy_sensitivity_yaw.value * host_frametime; + cl.viewangles[PITCH] += lookEased.y * joy_sensitivity_pitch.value * (joy_invert.value ? -1.0 : 1.0) * host_frametime; + + if (lookEased.x != 0 || lookEased.y != 0) + V_StopPitchDrift(); + + /* johnfitz -- variable pitch clamping */ + if (cl.viewangles[PITCH] > cl_maxpitch.value) + cl.viewangles[PITCH] = cl_maxpitch.value; + if (cl.viewangles[PITCH] < cl_minpitch.value) + cl.viewangles[PITCH] = cl_minpitch.value; +#endif +} + +void IN_MouseMove(usercmd_t *cmd) +{ + float dmx, dmy; + + dmx = total_dx * sensitivity.value; + dmy = total_dy * sensitivity.value; + + total_dx = 0; + total_dy = 0; + + // do pause check after resetting total_d* so mouse movements during pause don't accumulate + if (cl.paused || key_dest != key_game) + return; + + if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) )) + cmd->sidemove += m_side.value * dmx; + else + cl.viewangles[YAW] -= m_yaw.value * dmx; + + if (in_mlook.state & 1) + { + if (dmx || dmy) + V_StopPitchDrift (); + } + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + cl.viewangles[PITCH] += m_pitch.value * dmy; + /* johnfitz -- variable pitch clamping */ + if (cl.viewangles[PITCH] > cl_maxpitch.value) + cl.viewangles[PITCH] = cl_maxpitch.value; + if (cl.viewangles[PITCH] < cl_minpitch.value) + cl.viewangles[PITCH] = cl_minpitch.value; + } + else + { + if ((in_strafe.state & 1) && noclip_anglehack) + cmd->upmove -= m_forward.value * dmy; + else + cmd->forwardmove -= m_forward.value * dmy; + } +} + +void IN_Move(usercmd_t *cmd) +{ + IN_JoyMove(cmd); + IN_MouseMove(cmd); +} + +void IN_ClearStates (void) +{ +} + +void IN_UpdateInputMode (void) +{ + qboolean want_textmode = Key_TextEntry(); + if (textmode != want_textmode) + { + textmode = want_textmode; +#if !defined(USE_SDL2) + SDL_EnableUNICODE(textmode); + if (in_debugkeys.value) + Con_Printf("SDL_EnableUNICODE %d time: %g\n", textmode, Sys_DoubleTime()); +#else + if (textmode) + { + SDL_StartTextInput(); + if (in_debugkeys.value) + Con_Printf("SDL_StartTextInput time: %g\n", Sys_DoubleTime()); + } + else + { + SDL_StopTextInput(); + if (in_debugkeys.value) + Con_Printf("SDL_StopTextInput time: %g\n", Sys_DoubleTime()); + } +#endif + } +} + +#if !defined(USE_SDL2) +static inline int IN_SDL_KeysymToQuakeKey(SDLKey sym) +{ + if (sym > SDLK_SPACE && sym < SDLK_DELETE) + return sym; + + switch (sym) + { + case SDLK_TAB: return K_TAB; + case SDLK_RETURN: return K_ENTER; + case SDLK_ESCAPE: return K_ESCAPE; + case SDLK_SPACE: return K_SPACE; + + case SDLK_BACKSPACE: return K_BACKSPACE; + case SDLK_UP: return K_UPARROW; + case SDLK_DOWN: return K_DOWNARROW; + case SDLK_LEFT: return K_LEFTARROW; + case SDLK_RIGHT: return K_RIGHTARROW; + + case SDLK_LALT: return K_ALT; + case SDLK_RALT: return K_ALT; + case SDLK_LCTRL: return K_CTRL; + case SDLK_RCTRL: return K_CTRL; + case SDLK_LSHIFT: return K_SHIFT; + case SDLK_RSHIFT: return K_SHIFT; + + case SDLK_F1: return K_F1; + case SDLK_F2: return K_F2; + case SDLK_F3: return K_F3; + case SDLK_F4: return K_F4; + case SDLK_F5: return K_F5; + case SDLK_F6: return K_F6; + case SDLK_F7: return K_F7; + case SDLK_F8: return K_F8; + case SDLK_F9: return K_F9; + case SDLK_F10: return K_F10; + case SDLK_F11: return K_F11; + case SDLK_F12: return K_F12; + case SDLK_INSERT: return K_INS; + case SDLK_DELETE: return K_DEL; + case SDLK_PAGEDOWN: return K_PGDN; + case SDLK_PAGEUP: return K_PGUP; + case SDLK_HOME: return K_HOME; + case SDLK_END: return K_END; + + case SDLK_NUMLOCK: return K_KP_NUMLOCK; + case SDLK_KP_DIVIDE: return K_KP_SLASH; + case SDLK_KP_MULTIPLY: return K_KP_STAR; + case SDLK_KP_MINUS:return K_KP_MINUS; + case SDLK_KP7: return K_KP_HOME; + case SDLK_KP8: return K_KP_UPARROW; + case SDLK_KP9: return K_KP_PGUP; + case SDLK_KP_PLUS: return K_KP_PLUS; + case SDLK_KP4: return K_KP_LEFTARROW; + case SDLK_KP5: return K_KP_5; + case SDLK_KP6: return K_KP_RIGHTARROW; + case SDLK_KP1: return K_KP_END; + case SDLK_KP2: return K_KP_DOWNARROW; + case SDLK_KP3: return K_KP_PGDN; + case SDLK_KP_ENTER: return K_KP_ENTER; + case SDLK_KP0: return K_KP_INS; + case SDLK_KP_PERIOD: return K_KP_DEL; + + case SDLK_LMETA: return K_COMMAND; + case SDLK_RMETA: return K_COMMAND; + + case SDLK_BREAK: return K_PAUSE; + case SDLK_PAUSE: return K_PAUSE; + + case SDLK_WORLD_18: return '~'; // the '�' key + + default: return 0; + } +} +#endif + +#if defined(USE_SDL2) +static inline int IN_SDL2_ScancodeToQuakeKey(SDL_Scancode scancode) +{ + switch (scancode) + { + case SDL_SCANCODE_TAB: return K_TAB; + case SDL_SCANCODE_RETURN: return K_ENTER; + case SDL_SCANCODE_RETURN2: return K_ENTER; + case SDL_SCANCODE_ESCAPE: return K_ESCAPE; + case SDL_SCANCODE_SPACE: return K_SPACE; + + case SDL_SCANCODE_A: return 'a'; + case SDL_SCANCODE_B: return 'b'; + case SDL_SCANCODE_C: return 'c'; + case SDL_SCANCODE_D: return 'd'; + case SDL_SCANCODE_E: return 'e'; + case SDL_SCANCODE_F: return 'f'; + case SDL_SCANCODE_G: return 'g'; + case SDL_SCANCODE_H: return 'h'; + case SDL_SCANCODE_I: return 'i'; + case SDL_SCANCODE_J: return 'j'; + case SDL_SCANCODE_K: return 'k'; + case SDL_SCANCODE_L: return 'l'; + case SDL_SCANCODE_M: return 'm'; + case SDL_SCANCODE_N: return 'n'; + case SDL_SCANCODE_O: return 'o'; + case SDL_SCANCODE_P: return 'p'; + case SDL_SCANCODE_Q: return 'q'; + case SDL_SCANCODE_R: return 'r'; + case SDL_SCANCODE_S: return 's'; + case SDL_SCANCODE_T: return 't'; + case SDL_SCANCODE_U: return 'u'; + case SDL_SCANCODE_V: return 'v'; + case SDL_SCANCODE_W: return 'w'; + case SDL_SCANCODE_X: return 'x'; + case SDL_SCANCODE_Y: return 'y'; + case SDL_SCANCODE_Z: return 'z'; + + case SDL_SCANCODE_1: return '1'; + case SDL_SCANCODE_2: return '2'; + case SDL_SCANCODE_3: return '3'; + case SDL_SCANCODE_4: return '4'; + case SDL_SCANCODE_5: return '5'; + case SDL_SCANCODE_6: return '6'; + case SDL_SCANCODE_7: return '7'; + case SDL_SCANCODE_8: return '8'; + case SDL_SCANCODE_9: return '9'; + case SDL_SCANCODE_0: return '0'; + + case SDL_SCANCODE_MINUS: return '-'; + case SDL_SCANCODE_EQUALS: return '='; + case SDL_SCANCODE_LEFTBRACKET: return '['; + case SDL_SCANCODE_RIGHTBRACKET: return ']'; + case SDL_SCANCODE_BACKSLASH: return '\\'; + case SDL_SCANCODE_NONUSHASH: return '#'; + case SDL_SCANCODE_SEMICOLON: return ';'; + case SDL_SCANCODE_APOSTROPHE: return '\''; + case SDL_SCANCODE_GRAVE: return '`'; + case SDL_SCANCODE_COMMA: return ','; + case SDL_SCANCODE_PERIOD: return '.'; + case SDL_SCANCODE_SLASH: return '/'; + case SDL_SCANCODE_NONUSBACKSLASH: return '\\'; + + case SDL_SCANCODE_BACKSPACE: return K_BACKSPACE; + case SDL_SCANCODE_UP: return K_UPARROW; + case SDL_SCANCODE_DOWN: return K_DOWNARROW; + case SDL_SCANCODE_LEFT: return K_LEFTARROW; + case SDL_SCANCODE_RIGHT: return K_RIGHTARROW; + + case SDL_SCANCODE_LALT: return K_ALT; + case SDL_SCANCODE_RALT: return K_ALT; + case SDL_SCANCODE_LCTRL: return K_CTRL; + case SDL_SCANCODE_RCTRL: return K_CTRL; + case SDL_SCANCODE_LSHIFT: return K_SHIFT; + case SDL_SCANCODE_RSHIFT: return K_SHIFT; + + case SDL_SCANCODE_F1: return K_F1; + case SDL_SCANCODE_F2: return K_F2; + case SDL_SCANCODE_F3: return K_F3; + case SDL_SCANCODE_F4: return K_F4; + case SDL_SCANCODE_F5: return K_F5; + case SDL_SCANCODE_F6: return K_F6; + case SDL_SCANCODE_F7: return K_F7; + case SDL_SCANCODE_F8: return K_F8; + case SDL_SCANCODE_F9: return K_F9; + case SDL_SCANCODE_F10: return K_F10; + case SDL_SCANCODE_F11: return K_F11; + case SDL_SCANCODE_F12: return K_F12; + case SDL_SCANCODE_INSERT: return K_INS; + case SDL_SCANCODE_DELETE: return K_DEL; + case SDL_SCANCODE_PAGEDOWN: return K_PGDN; + case SDL_SCANCODE_PAGEUP: return K_PGUP; + case SDL_SCANCODE_HOME: return K_HOME; + case SDL_SCANCODE_END: return K_END; + + case SDL_SCANCODE_NUMLOCKCLEAR: return K_KP_NUMLOCK; + case SDL_SCANCODE_KP_DIVIDE: return K_KP_SLASH; + case SDL_SCANCODE_KP_MULTIPLY: return K_KP_STAR; + case SDL_SCANCODE_KP_MINUS: return K_KP_MINUS; + case SDL_SCANCODE_KP_7: return K_KP_HOME; + case SDL_SCANCODE_KP_8: return K_KP_UPARROW; + case SDL_SCANCODE_KP_9: return K_KP_PGUP; + case SDL_SCANCODE_KP_PLUS: return K_KP_PLUS; + case SDL_SCANCODE_KP_4: return K_KP_LEFTARROW; + case SDL_SCANCODE_KP_5: return K_KP_5; + case SDL_SCANCODE_KP_6: return K_KP_RIGHTARROW; + case SDL_SCANCODE_KP_1: return K_KP_END; + case SDL_SCANCODE_KP_2: return K_KP_DOWNARROW; + case SDL_SCANCODE_KP_3: return K_KP_PGDN; + case SDL_SCANCODE_KP_ENTER: return K_KP_ENTER; + case SDL_SCANCODE_KP_0: return K_KP_INS; + case SDL_SCANCODE_KP_PERIOD: return K_KP_DEL; + + case SDL_SCANCODE_LGUI: return K_COMMAND; + case SDL_SCANCODE_RGUI: return K_COMMAND; + + case SDL_SCANCODE_PAUSE: return K_PAUSE; + + default: return 0; + } +} +#endif + +#if defined(USE_SDL2) +static void IN_DebugTextEvent(SDL_Event *event) +{ + Con_Printf ("SDL_TEXTINPUT '%s' time: %g\n", event->text.text, Sys_DoubleTime()); +} +#endif + +static void IN_DebugKeyEvent(SDL_Event *event) +{ + const char *eventtype = (event->key.state == SDL_PRESSED) ? "SDL_KEYDOWN" : "SDL_KEYUP"; +#if defined(USE_SDL2) + Con_Printf ("%s scancode: '%s' keycode: '%s' time: %g\n", + eventtype, + SDL_GetScancodeName(event->key.keysym.scancode), + SDL_GetKeyName(event->key.keysym.sym), + Sys_DoubleTime()); +#else + Con_Printf ("%s sym: '%s' unicode: %04x time: %g\n", + eventtype, + SDL_GetKeyName(event->key.keysym.sym), + (int)event->key.keysym.unicode, + Sys_DoubleTime()); +#endif +} + +void IN_SendKeyEvents (void) +{ + SDL_Event event; + int key; + qboolean down; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { +#if defined(USE_SDL2) + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) + { + windowhasfocus = true; + S_UnblockSound(); + } + else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) + { + windowhasfocus = false; + S_BlockSound(); + } + break; +#else + case SDL_ACTIVEEVENT: + if (event.active.state & (SDL_APPINPUTFOCUS|SDL_APPACTIVE)) + { + if (event.active.gain) + { + windowhasfocus = true; + S_UnblockSound(); + } + else + { + windowhasfocus = false; + S_BlockSound(); + } + } + break; +#endif +#if defined(USE_SDL2) + case SDL_TEXTINPUT: + if (in_debugkeys.value) + IN_DebugTextEvent(&event); + + // SDL2: We use SDL_TEXTINPUT for typing in the console / chat. + // SDL2 uses the local keyboard layout and handles modifiers + // (shift for uppercase, etc.) for us. + { + unsigned char *ch; + for (ch = (unsigned char *)event.text.text; *ch; ch++) + if ((*ch & ~0x7F) == 0) + Char_Event (*ch); + } + break; +#endif + case SDL_KEYDOWN: + case SDL_KEYUP: + down = (event.key.state == SDL_PRESSED); + + if (in_debugkeys.value) + IN_DebugKeyEvent(&event); + +#if defined(USE_SDL2) + // SDL2: we interpret the keyboard as the US layout, so keybindings + // are based on key position, not the label on the key cap. + key = IN_SDL2_ScancodeToQuakeKey(event.key.keysym.scancode); +#else + key = IN_SDL_KeysymToQuakeKey(event.key.keysym.sym); +#endif + + // also pass along the underlying keycode using the proper current layout for Y/N prompts. + Key_EventWithKeycode (key, down, event.key.keysym.sym); + +#if !defined(USE_SDL2) + if (down && (event.key.keysym.unicode & ~0x7F) == 0) + Char_Event (event.key.keysym.unicode); +#endif + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (event.button.button < 1 || + event.button.button > Q_COUNTOF(buttonremap)) + { + Con_Printf ("Ignored event for mouse button %d\n", + event.button.button); + break; + } + Key_Event(buttonremap[event.button.button - 1], event.button.state == SDL_PRESSED); + break; + +#if defined(USE_SDL2) + case SDL_MOUSEWHEEL: + if (event.wheel.y > 0) + { + Key_Event(K_MWHEELUP, true); + Key_Event(K_MWHEELUP, false); + } + else if (event.wheel.y < 0) + { + Key_Event(K_MWHEELDOWN, true); + Key_Event(K_MWHEELDOWN, false); + } + break; +#endif + + case SDL_MOUSEMOTION: + IN_MouseMotion(event.motion.xrel, event.motion.yrel); + break; + +#if defined(USE_SDL2) + case SDL_CONTROLLERDEVICEADDED: + if (joy_active_instaceid == -1) + { + joy_active_controller = SDL_GameControllerOpen(event.cdevice.which); + if (joy_active_controller == NULL) + Con_DPrintf("Couldn't open game controller\n"); + else + { + SDL_Joystick *joy; + joy = SDL_GameControllerGetJoystick(joy_active_controller); + joy_active_instaceid = SDL_JoystickInstanceID(joy); + } + } + else + Con_DPrintf("Ignoring SDL_CONTROLLERDEVICEADDED\n"); + break; + case SDL_CONTROLLERDEVICEREMOVED: + if (joy_active_instaceid != -1 && event.cdevice.which == joy_active_instaceid) + { + SDL_GameControllerClose(joy_active_controller); + joy_active_controller = NULL; + joy_active_instaceid = -1; + } + else + Con_DPrintf("Ignoring SDL_CONTROLLERDEVICEREMOVED\n"); + break; + case SDL_CONTROLLERDEVICEREMAPPED: + Con_DPrintf("Ignoring SDL_CONTROLLERDEVICEREMAPPED\n"); + break; +#endif + + case SDL_QUIT: + CL_Disconnect (); + Sys_Quit (); + break; + + default: + break; + } + } +} + diff --git a/Quake/input.hpp b/Quake/input.hpp new file mode 100644 index 0000000..212d4cb --- /dev/null +++ b/Quake/input.hpp @@ -0,0 +1,56 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef QUAKE_INPUT_H +#define QUAKE_INPUT_H + +// input.h -- external (non-keyboard) input devices + +void IN_Init (void); + +void IN_Shutdown (void); + +void IN_Commands (void); +// oportunity for devices to stick commands on the script buffer + +// mouse moved by dx and dy pixels +void IN_MouseMotion(int dx, int dy); + + +void IN_SendKeyEvents (void); +// used as a callback for Sys_SendKeyEvents() by some drivers + +void IN_UpdateInputMode (void); +// do stuff if input mode (text/non-text) changes matter to the keyboard driver + +void IN_Move (usercmd_t *cmd); +// add additional movement on top of the keyboard move cmd + +void IN_ClearStates (void); +// restores all button and position states to defaults + +// called when the app becomes active +void IN_Activate (void); + +// called when the app becomes inactive +void IN_Deactivate (qboolean free_cursor); + +#endif diff --git a/Quake/keys.cpp b/Quake/keys.cpp new file mode 100644 index 0000000..2a5e076 --- /dev/null +++ b/Quake/keys.cpp @@ -0,0 +1,1239 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" +#include "arch_def.hpp" + +/* key up events are sent even if in console mode */ + +#define HISTORY_FILE_NAME "history.txt" + +char key_lines[CMDLINES][MAXCMDLINE]; + +int key_linepos; +int key_insert; //johnfitz -- insert key toggle (for editing) +double key_blinktime; //johnfitz -- fudge cursor blinking to make it easier to spot in certain cases + +int edit_line = 0; +int history_line = 0; + +keydest_t key_dest; + +char *keybindings[MAX_KEYS]; +qboolean consolekeys[MAX_KEYS]; // if true, can't be rebound while in console +qboolean menubound[MAX_KEYS]; // if true, can't be rebound while in menu +qboolean keydown[MAX_KEYS]; + +typedef struct +{ + const char *name; + int keynum; +} keyname_t; + +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + +// {"KP_NUMLOCK", K_KP_NUMLOCK}, + {"KP_SLASH", K_KP_SLASH}, + {"KP_STAR", K_KP_STAR}, + {"KP_MINUS", K_KP_MINUS}, + {"KP_HOME", K_KP_HOME}, + {"KP_UPARROW", K_KP_UPARROW}, + {"KP_PGUP", K_KP_PGUP}, + {"KP_PLUS", K_KP_PLUS}, + {"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}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"COMMAND", K_COMMAND}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + {"AUX17", K_AUX17}, + {"AUX18", K_AUX18}, + {"AUX19", K_AUX19}, + {"AUX20", K_AUX20}, + {"AUX21", K_AUX21}, + {"AUX22", K_AUX22}, + {"AUX23", K_AUX23}, + {"AUX24", K_AUX24}, + {"AUX25", K_AUX25}, + {"AUX26", K_AUX26}, + {"AUX27", K_AUX27}, + {"AUX28", K_AUX28}, + {"AUX29", K_AUX29}, + {"AUX30", K_AUX30}, + {"AUX31", K_AUX31}, + {"AUX32", K_AUX32}, + + {"PAUSE", K_PAUSE}, + + {"MWHEELUP", K_MWHEELUP}, + {"MWHEELDOWN", K_MWHEELDOWN}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {"BACKQUOTE", '`'}, // because a raw backquote may toggle the console + {"TILDE", '~'}, // because a raw tilde may toggle the console + + {"LTHUMB", K_LTHUMB}, + {"RTHUMB", K_RTHUMB}, + {"LSHOULDER", K_LSHOULDER}, + {"RSHOULDER", K_RSHOULDER}, + {"ABUTTON", K_ABUTTON}, + {"BBUTTON", K_BBUTTON}, + {"XBUTTON", K_XBUTTON}, + {"YBUTTON", K_YBUTTON}, + {"LTRIGGER", K_LTRIGGER}, + {"RTRIGGER", K_RTRIGGER}, + + {NULL, 0} +}; + +/* +============================================================================== + + LINE TYPING INTO THE CONSOLE + +============================================================================== +*/ + +static void PasteToConsole (void) +{ + char *cbd, *p, *workline; + int mvlen, inslen; + + if (key_linepos == MAXCMDLINE - 1) + return; + + if ((cbd = PL_GetClipboardData()) == NULL) + return; + + p = cbd; + while (*p) + { + if (*p == '\n' || *p == '\r' || *p == '\b') + { + *p = 0; + break; + } + p++; + } + + inslen = (int) (p - cbd); + if (inslen + key_linepos > MAXCMDLINE - 1) + inslen = MAXCMDLINE - 1 - key_linepos; + if (inslen <= 0) goto done; + + workline = key_lines[edit_line]; + workline += key_linepos; + mvlen = (int) strlen(workline); + if (mvlen + inslen + key_linepos > MAXCMDLINE - 1) + { + mvlen = MAXCMDLINE - 1 - key_linepos - inslen; + if (mvlen < 0) mvlen = 0; + } + + // insert the string + if (mvlen != 0) + memmove (workline + inslen, workline, mvlen); + memcpy (workline, cbd, inslen); + key_linepos += inslen; + workline[mvlen + inslen] = '\0'; + done: + Z_Free(cbd); +} + +/* +==================== +Key_Console -- johnfitz -- heavy revision + +Interactive line editing and console scrollback +==================== +*/ +extern char *con_text, key_tabpartial[MAXCMDLINE]; +extern int con_current, con_linewidth, con_vislines; + +void Key_Console (int key) +{ + static char current[MAXCMDLINE] = ""; + int history_line_last; + size_t len; + char *workline = key_lines[edit_line]; + + switch (key) + { + case K_ENTER: + case K_KP_ENTER: + key_tabpartial[0] = 0; + Cbuf_AddText (workline + 1); // skip the prompt + Cbuf_AddText ("\n"); + Con_Printf ("%s\n", workline); + + // If the last two lines are identical, skip storing this line in history + // by not incrementing edit_line + if (strcmp(workline, key_lines[(edit_line - 1) & (CMDLINES - 1)])) + edit_line = (edit_line + 1) & (CMDLINES - 1); + + history_line = edit_line; + key_lines[edit_line][0] = ']'; + key_lines[edit_line][1] = 0; //johnfitz -- otherwise old history items show up in the new edit line + key_linepos = 1; + if (cls.state == ca_disconnected) + SCR_UpdateScreen (); // force an update, because the command may take some time + return; + + case K_TAB: + Con_TabComplete (); + return; + + case K_BACKSPACE: + key_tabpartial[0] = 0; + if (key_linepos > 1) + { + workline += key_linepos - 1; + if (workline[1]) + { + len = strlen(workline); + memmove (workline, workline + 1, len); + } + else *workline = 0; + key_linepos--; + } + return; + + case K_DEL: + key_tabpartial[0] = 0; + workline += key_linepos; + if (*workline) + { + if (workline[1]) + { + len = strlen(workline); + memmove (workline, workline + 1, len); + } + else *workline = 0; + } + return; + + case K_HOME: + if (keydown[K_CTRL]) + { + //skip initial empty lines + int i, x; + char *line; + + for (i = con_current - con_totallines + 1; i <= con_current; i++) + { + line = con_text + (i % con_totallines) * con_linewidth; + for (x = 0; x < con_linewidth; x++) + { + if (line[x] != ' ') + break; + } + if (x != con_linewidth) + break; + } + con_backscroll = CLAMP(0, con_current-i%con_totallines-2, con_totallines-(glheight>>3)-1); + } + else key_linepos = 1; + return; + + case K_END: + if (keydown[K_CTRL]) + con_backscroll = 0; + else key_linepos = strlen(workline); + return; + + case K_PGUP: + case K_MWHEELUP: + con_backscroll += keydown[K_CTRL] ? ((con_vislines>>3) - 4) : 2; + if (con_backscroll > con_totallines - (vid.height>>3) - 1) + con_backscroll = con_totallines - (vid.height>>3) - 1; + return; + + case K_PGDN: + case K_MWHEELDOWN: + con_backscroll -= keydown[K_CTRL] ? ((con_vislines>>3) - 4) : 2; + if (con_backscroll < 0) + con_backscroll = 0; + return; + + case K_LEFTARROW: + if (key_linepos > 1) + { + key_linepos--; + key_blinktime = realtime; + } + return; + + case K_RIGHTARROW: + len = strlen(workline); + if ((int)len == key_linepos) + { + len = strlen(key_lines[(edit_line + (CMDLINES - 1)) & (CMDLINES - 1)]); + if ((int)len <= key_linepos) + return; // no character to get + workline += key_linepos; + *workline = key_lines[(edit_line + (CMDLINES - 1)) & (CMDLINES - 1)][key_linepos]; + workline[1] = 0; + key_linepos++; + } + else + { + key_linepos++; + key_blinktime = realtime; + } + return; + + case K_UPARROW: + if (history_line == edit_line) + Q_strcpy(current, workline); + + history_line_last = history_line; + do + { + history_line = (history_line - 1) & (CMDLINES - 1); + } while (history_line != edit_line && !key_lines[history_line][1]); + + if (history_line == edit_line) + { + history_line = history_line_last; + return; + } + + key_tabpartial[0] = 0; + len = strlen(key_lines[history_line]); + memmove(workline, key_lines[history_line], len+1); + key_linepos = (int)len; + return; + + case K_DOWNARROW: + if (history_line == edit_line) + return; + + key_tabpartial[0] = 0; + + do + { + history_line = (history_line + 1) & (CMDLINES - 1); + } while (history_line != edit_line && !key_lines[history_line][1]); + + if (history_line == edit_line) + { + len = strlen(current); + memcpy(workline, current, len+1); + } + else + { + len = strlen(key_lines[history_line]); + memmove(workline, key_lines[history_line], len+1); + } + key_linepos = (int)len; + return; + + case K_INS: + if (keydown[K_SHIFT]) /* Shift-Ins paste */ + PasteToConsole(); + else key_insert ^= 1; + return; + + case 'v': + case 'V': +#if defined(PLATFORM_OSX) || defined(PLATFORM_MAC) + if (keydown[K_COMMAND]) { /* Cmd+v paste (Mac-only) */ + PasteToConsole(); + return; + } +#endif + if (keydown[K_CTRL]) { /* Ctrl+v paste */ + PasteToConsole(); + return; + } + break; + + case 'c': + case 'C': + if (keydown[K_CTRL]) { /* Ctrl+C: abort the line -- S.A */ + Con_Printf ("%s\n", workline); + workline[0] = ']'; + workline[1] = 0; + key_linepos = 1; + history_line= edit_line; + return; + } + break; + } +} + +void Char_Console (int key) +{ + size_t len; + char *workline = key_lines[edit_line]; + + if (key_linepos < MAXCMDLINE-1) + { + qboolean endpos = !workline[key_linepos]; + + key_tabpartial[0] = 0; //johnfitz + // if inserting, move the text to the right + if (key_insert && !endpos) + { + workline[MAXCMDLINE - 2] = 0; + workline += key_linepos; + len = strlen(workline) + 1; + #if defined(__GNUC__) && (__GNUC__ > 7) + #pragma GCC diagnostic ignored "-Warray-bounds" + #endif + memmove (workline + 1, workline, len); + #if defined(__GNUC__) && (__GNUC__ > 7) + #pragma GCC diagnostic pop + #endif + *workline = key; + } + else + { + workline += key_linepos; + *workline = key; + // null terminate if at the end + if (endpos) + workline[1] = 0; + } + key_linepos++; + } +} + +//============================================================================ + +qboolean chat_team = false; +static char chat_buffer[MAXCMDLINE]; +static int chat_bufferlen = 0; + +const char *Key_GetChatBuffer (void) +{ + return chat_buffer; +} + +int Key_GetChatMsgLen (void) +{ + return chat_bufferlen; +} + +void Key_EndChat (void) +{ + key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; +} + +void Key_Message (int key) +{ + switch (key) + { + case K_ENTER: + case K_KP_ENTER: + if (chat_team) + Cbuf_AddText ("say_team \""); + else + Cbuf_AddText ("say \""); + Cbuf_AddText(chat_buffer); + Cbuf_AddText("\"\n"); + + Key_EndChat (); + return; + + case K_ESCAPE: + Key_EndChat (); + return; + + case K_BACKSPACE: + if (chat_bufferlen) + chat_buffer[--chat_bufferlen] = 0; + return; + } +} + +void Char_Message (int key) +{ + if (chat_bufferlen == sizeof(chat_buffer) - 1) + return; // all full + + chat_buffer[chat_bufferlen++] = key; + chat_buffer[chat_bufferlen] = 0; +} + +//============================================================================ + + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keybindings[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. +=================== +*/ +int Key_StringToKeynum (const char *str) +{ + keyname_t *kn; + + if (!str || !str[0]) + return -1; + if (!str[1]) + return str[0]; + + for (kn=keynames ; kn->name ; kn++) + { + if (!q_strcasecmp(str,kn->name)) + return kn->keynum; + } + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, or a K_* name) for the +given keynum. +FIXME: handle quote special (general escape sequence?) +=================== +*/ +const char *Key_KeynumToString (int keynum) +{ + static char tinystr[2]; + keyname_t *kn; + + if (keynum == -1) + return ""; + if (keynum > 32 && keynum < 127) + { // printable ascii + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + for (kn = keynames; kn->name; kn++) + { + if (keynum == kn->keynum) + return kn->name; + } + + return ""; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding (int keynum, const char *binding) +{ + if (keynum == -1) + return; + +// free old bindings + if (keybindings[keynum]) + { + Z_Free (keybindings[keynum]); + keybindings[keynum] = NULL; + } + +// allocate memory for new binding + if (binding) + keybindings[keynum] = Z_Strdup(binding); +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Con_Printf ("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b == -1) + { + Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + Key_SetBinding (b, NULL); +} + +void Key_Unbindall_f (void) +{ + int i; + + for (i = 0; i < MAX_KEYS; i++) + { + if (keybindings[i]) + Key_SetBinding (i, NULL); + } +} + +/* +============ +Key_Bindlist_f -- johnfitz +============ +*/ +void Key_Bindlist_f (void) +{ + int i, count; + + count = 0; + for (i = 0; i < MAX_KEYS; i++) + { + if (keybindings[i] && *keybindings[i]) + { + Con_SafePrintf (" %s \"%s\"\n", Key_KeynumToString(i), keybindings[i]); + count++; + } + } + Con_SafePrintf ("%i bindings\n", count); +} + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c != 2 && c != 3) + { + Con_Printf ("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b == -1) + { + Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + if (c == 2) + { + if (keybindings[b]) + Con_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] ); + else + Con_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; + for (i = 2; i < c; i++) + { + q_strlcat (cmd, Cmd_Argv(i), sizeof(cmd)); + if (i != (c-1)) + q_strlcat (cmd, " ", sizeof(cmd)); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings (FILE *f) +{ + int i; + + // unbindall before loading stored bindings: + if (cfg_unbindall.value) + fprintf (f, "unbindall\n"); + for (i = 0; i < MAX_KEYS; i++) + { + if (keybindings[i] && *keybindings[i]) + fprintf (f, "bind \"%s\" \"%s\"\n", Key_KeynumToString(i), keybindings[i]); + } +} + + +void History_Init (void) +{ + int i, c; + FILE *hf; + + for (i = 0; i < CMDLINES; i++) + { + key_lines[i][0] = ']'; + key_lines[i][1] = 0; + } + key_linepos = 1; + + hf = fopen(va("%s/%s", host_parms->userdir, HISTORY_FILE_NAME), "rt"); + if (hf != NULL) + { + do + { + i = 1; + do + { + c = fgetc(hf); + key_lines[edit_line][i++] = c; + } while (c != '\r' && c != '\n' && c != EOF && i < MAXCMDLINE); + key_lines[edit_line][i - 1] = 0; + edit_line = (edit_line + 1) & (CMDLINES - 1); + /* for people using a windows-generated history file on unix: */ + if (c == '\r' || c == '\n') + { + do + c = fgetc(hf); + while (c == '\r' || c == '\n'); + if (c != EOF) + ungetc(c, hf); + else c = 0; /* loop once more, otherwise last line is lost */ + } + } while (c != EOF && edit_line < CMDLINES); + fclose(hf); + + history_line = edit_line = (edit_line - 1) & (CMDLINES - 1); + key_lines[edit_line][0] = ']'; + key_lines[edit_line][1] = 0; + } +} + +void History_Shutdown (void) +{ + int i; + FILE *hf; + + hf = fopen(va("%s/%s", host_parms->userdir, HISTORY_FILE_NAME), "wt"); + if (hf != NULL) + { + i = edit_line; + do + { + i = (i + 1) & (CMDLINES - 1); + } while (i != edit_line && !key_lines[i][1]); + + while (i != edit_line && key_lines[i][1]) + { + fprintf(hf, "%s\n", key_lines[i] + 1); + i = (i + 1) & (CMDLINES - 1); + } + fclose(hf); + } +} + +/* +=================== +Key_Init +=================== +*/ +void Key_Init (void) +{ + int i; + + History_Init (); + + key_blinktime = realtime; //johnfitz + +// +// initialize consolekeys[] +// + for (i = 32; i < 127; i++) // ascii characters + consolekeys[i] = true; + consolekeys['`'] = false; + consolekeys['~'] = false; + consolekeys[K_TAB] = true; + consolekeys[K_ENTER] = true; + consolekeys[K_ESCAPE] = true; + consolekeys[K_BACKSPACE] = true; + consolekeys[K_UPARROW] = true; + consolekeys[K_DOWNARROW] = true; + consolekeys[K_LEFTARROW] = true; + consolekeys[K_RIGHTARROW] = true; + consolekeys[K_CTRL] = true; + consolekeys[K_SHIFT] = true; + consolekeys[K_INS] = true; + consolekeys[K_DEL] = true; + consolekeys[K_PGDN] = true; + consolekeys[K_PGUP] = true; + consolekeys[K_HOME] = true; + consolekeys[K_END] = true; + consolekeys[K_KP_NUMLOCK] = true; + consolekeys[K_KP_SLASH] = true; + consolekeys[K_KP_STAR] = true; + consolekeys[K_KP_MINUS] = true; + consolekeys[K_KP_HOME] = true; + consolekeys[K_KP_UPARROW] = true; + consolekeys[K_KP_PGUP] = true; + consolekeys[K_KP_PLUS] = true; + consolekeys[K_KP_LEFTARROW] = true; + consolekeys[K_KP_5] = true; + consolekeys[K_KP_RIGHTARROW] = true; + consolekeys[K_KP_END] = true; + consolekeys[K_KP_DOWNARROW] = true; + consolekeys[K_KP_PGDN] = true; + consolekeys[K_KP_ENTER] = true; + consolekeys[K_KP_INS] = true; + consolekeys[K_KP_DEL] = true; +#if defined(PLATFORM_OSX) || defined(PLATFORM_MAC) + consolekeys[K_COMMAND] = true; +#endif + consolekeys[K_MWHEELUP] = true; + consolekeys[K_MWHEELDOWN] = true; + +// +// initialize menubound[] +// + menubound[K_ESCAPE] = true; + for (i = 0; i < 12; i++) + menubound[K_F1+i] = true; + +// +// register our functions +// + Cmd_AddCommand ("bindlist",Key_Bindlist_f); //johnfitz + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); +} + +static struct { + qboolean active; + int lastkey; + int lastchar; +} key_inputgrab = { false, -1, -1 }; + +/* +=================== +Key_BeginInputGrab +=================== +*/ +void Key_BeginInputGrab (void) +{ + Key_ClearStates (); + + key_inputgrab.active = true; + key_inputgrab.lastkey = -1; + key_inputgrab.lastchar = -1; + + IN_UpdateInputMode (); +} + +/* +=================== +Key_EndInputGrab +=================== +*/ +void Key_EndInputGrab (void) +{ + Key_ClearStates (); + + key_inputgrab.active = false; + + IN_UpdateInputMode (); +} + +/* +=================== +Key_GetGrabbedInput +=================== +*/ +void Key_GetGrabbedInput (int *lastkey, int *lastchar) +{ + if (lastkey) + *lastkey = key_inputgrab.lastkey; + if (lastchar) + *lastchar = key_inputgrab.lastchar; +} + +/* +=================== +Key_Event + +Called by the system between frames for both key up and key down events +Should NOT be called during an interrupt! +=================== +*/ +void Key_Event (int key, qboolean down) +{ + Key_EventWithKeycode (key, down, 0); +} + +/* +=================== +Key_EventWithKeycode + +Called by the system between frames for both key up and key down events +Should NOT be called during an interrupt! +keycode parameter should have the key's actual keycode using the current keyboard layout, +not necessarily the US-keyboard-based scancode. Pass 0 if not applicable. +=================== +*/ +void Key_EventWithKeycode (int key, qboolean down, int keycode) +{ + char *kb; + char cmd[1024]; + + if (key < 0 || key >= MAX_KEYS) + return; + +// handle fullscreen toggle + if (down && (key == K_ENTER || key == K_KP_ENTER) && keydown[K_ALT]) + { + VID_Toggle(); + return; + } + +// handle autorepeats and stray key up events + if (down) + { + if (keydown[key]) + { + if (key_dest == key_game && !con_forcedup) + return; // ignore autorepeats in game mode + } + else if (key >= 200 && !keybindings[key]) + Con_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString(key)); + } + else if (!keydown[key]) + return; // ignore stray key up events + + keydown[key] = down; + + if (key_inputgrab.active) + { + if (down) + { + key_inputgrab.lastkey = key; + if (keycode > 0) + key_inputgrab.lastchar = keycode; + } + return; + } + +// handle escape specialy, so the user can never unbind it + if (key == K_ESCAPE) + { + if (!down) + return; + + if (keydown[K_SHIFT]) + { + Con_ToggleConsole_f(); + return; + } + + switch (key_dest) + { + case key_message: + Key_Message (key); + break; + case key_menu: + M_Keydown (key); + break; + case key_game: + case key_console: + M_ToggleMenu_f (); + break; + default: + Sys_Error ("Bad key_dest"); + } + + return; + } + +// key up events only generate commands if the game key binding is +// a button command (leading + sign). These will occur even in console mode, +// to keep the character from continuing an action started before a console +// switch. Button commands include the kenum as a parameter, so multiple +// downs can be matched with ups + if (!down) + { + kb = keybindings[key]; + if (kb && kb[0] == '+') + { + sprintf (cmd, "-%s %i\n", kb+1, key); + Cbuf_AddText (cmd); + } + return; + } + +// during demo playback, most keys bring up the main menu + if (cls.demoplayback && down && consolekeys[key] && key_dest == key_game && key != K_TAB) + { + M_ToggleMenu_f (); + return; + } + +// if not a consolekey, send to the interpreter no matter what mode is + if ((key_dest == key_menu && menubound[key]) || + (key_dest == key_console && !consolekeys[key]) || + (key_dest == key_game && (!con_forcedup || !consolekeys[key]))) + { + kb = keybindings[key]; + if (kb) + { + if (kb[0] == '+') + { // button commands add keynum as a parm + sprintf (cmd, "%s %i\n", kb, key); + Cbuf_AddText (cmd); + } + else + { + Cbuf_AddText (kb); + Cbuf_AddText ("\n"); + } + } + return; + } + + if (!down) + return; // other systems only care about key down events + + switch (key_dest) + { + case key_message: + Key_Message (key); + break; + case key_menu: + M_Keydown (key); + break; + + case key_game: + case key_console: + Key_Console (key); + break; + default: + Sys_Error ("Bad key_dest"); + } +} + +/* +=================== +Char_Event + +Called by the backend when the user has input a character. +=================== +*/ +void Char_Event (int key) +{ + if (key < 32 || key > 126) + return; + +#if defined(PLATFORM_OSX) || defined(PLATFORM_MAC) + if (keydown[K_COMMAND]) + return; +#endif + if (keydown[K_CTRL]) + return; + + if (key_inputgrab.active) + { + key_inputgrab.lastchar = key; + return; + } + + switch (key_dest) + { + case key_message: + Char_Message (key); + break; + case key_menu: + M_Charinput (key); + break; + case key_game: + if (!con_forcedup) + break; + /* fallthrough */ + case key_console: + Char_Console (key); + break; + default: + break; + } +} + +/* +=================== +Key_TextEntry +=================== +*/ +qboolean Key_TextEntry (void) +{ + if (key_inputgrab.active) + { + // This path is used for simple single-letter inputs (y/n prompts) that also + // accept controller input, so we don't want an onscreen keyboard for this case. + return false; + } + + switch (key_dest) + { + case key_message: + return true; + case key_menu: + return M_TextEntry(); + case key_game: + // Don't return true even during con_forcedup, because that happens while starting a + // game and we don't to trigger text input (and the onscreen keyboard on some devices) + // during this. + return false; + case key_console: + return true; + default: + return false; + } +} + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + + for (i = 0; i < MAX_KEYS; i++) + { + if (keydown[i]) + Key_Event (i, false); + } +} + +/* +=================== +Key_UpdateForDest +=================== +*/ +void Key_UpdateForDest (void) +{ + static qboolean forced = false; + + if (cls.state == ca_dedicated) + return; + + switch (key_dest) + { + case key_console: + if (forced && cls.state == ca_connected) + { + forced = false; + IN_Activate(); + key_dest = key_game; + } + break; + case key_game: + if (cls.state != ca_connected) + { + forced = true; + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + break; + } + /* fallthrough */ + default: + forced = false; + break; + } +} + diff --git a/Quake/keys.hpp b/Quake/keys.hpp new file mode 100644 index 0000000..29beddd --- /dev/null +++ b/Quake/keys.hpp @@ -0,0 +1,201 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_KEYS_H +#define _QUAKE_KEYS_H + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_NUMLOCK 153 +#define K_KP_SLASH 154 +#define K_KP_STAR 155 +#define K_KP_MINUS 156 +#define K_KP_HOME 157 +#define K_KP_UPARROW 158 +#define K_KP_PGUP 159 +#define K_KP_PLUS 160 +#define K_KP_LEFTARROW 161 +#define K_KP_5 162 +#define K_KP_RIGHTARROW 163 +#define K_KP_END 164 +#define K_KP_DOWNARROW 165 +#define K_KP_PGDN 166 +#define K_KP_ENTER 167 +#define K_KP_INS 168 +#define K_KP_DEL 169 + +#define K_COMMAND 170 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 200 +#define K_MOUSE2 201 +#define K_MOUSE3 202 + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// aux29-32: reserved for the HAT (POV) switch motion +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 + +// JACK: Intellimouse(c) Mouse Wheel Support + +#define K_MWHEELUP 239 +#define K_MWHEELDOWN 240 + +// thumb buttons +#define K_MOUSE4 241 +#define K_MOUSE5 242 + +// SDL2 game controller keys +#define K_LTHUMB 243 +#define K_RTHUMB 244 +#define K_LSHOULDER 245 +#define K_RSHOULDER 246 +#define K_ABUTTON 247 +#define K_BBUTTON 248 +#define K_XBUTTON 249 +#define K_YBUTTON 250 +#define K_LTRIGGER 251 +#define K_RTRIGGER 252 + +#define MAX_KEYS 256 + +#define MAXCMDLINE 256 + +typedef enum {key_game, key_console, key_message, key_menu} keydest_t; + +extern keydest_t key_dest; +extern char *keybindings[MAX_KEYS]; + +#define CMDLINES 64 + +extern char key_lines[CMDLINES][MAXCMDLINE]; +extern int edit_line; +extern int key_linepos; +extern int key_insert; +extern double key_blinktime; + +extern qboolean chat_team; + +void Key_Init (void); +void Key_ClearStates (void); +void Key_UpdateForDest (void); + +void Key_BeginInputGrab (void); +void Key_EndInputGrab (void); +void Key_GetGrabbedInput (int *lastkey, int *lastchar); + +void Key_Event (int key, qboolean down); +void Key_EventWithKeycode (int key, qboolean down, int keycode); +void Char_Event (int key); +qboolean Key_TextEntry (void); + +void Key_SetBinding (int keynum, const char *binding); +const char *Key_KeynumToString (int keynum); +void Key_WriteBindings (FILE *f); + +void Key_EndChat (void); +const char *Key_GetChatBuffer (void); +int Key_GetChatMsgLen (void); + +void History_Init (void); +void History_Shutdown (void); + +#endif /* _QUAKE_KEYS_H */ + diff --git a/Quake/lodepng.cpp b/Quake/lodepng.cpp new file mode 100644 index 0000000..98e4885 --- /dev/null +++ b/Quake/lodepng.cpp @@ -0,0 +1,6665 @@ +/* +LodePNG version 20220717 + +Copyright (c) 2005-2022 Lode Vandevenne + +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. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.hpp" + +#ifdef LODEPNG_COMPILE_DISK +#include /* LONG_MAX */ +#include /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20220717"; + +/* +This source file is divided into the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; /*success*/ +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + p->size = size; + return ucvector_reserve(p, size); +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +/*also appends null termination character*/ +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +#ifdef LODEPNG_COMPILE_DECODER /**/ +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} +#endif /**/ + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +The nbits parameter is unused but is given for documentation purposes, error +checking for amount of bits must be done beforehand. +*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer = reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {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}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {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}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {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}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {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}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (((size_t)1) << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (((size_t)1) << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol, reverse; + if(l == 0) continue; + symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + reverse = reverseBits(symbol, l); + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + advanceBits(reader, FIRSTBITS); + value += peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[value] - FIRSTBITS); + return codetree->table_value[value]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/ + ensureBits17(reader, 14); + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype, size_t max_output_size) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */ + int done = 0; + + if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + + while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This + appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/ + ensureBits32(reader, 30); + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) { + /*slightly faster code path if multiple literals in a row*/ + out->data[out->size++] = (unsigned char)code_ll; + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + } + if(code_ll <= 255) /*literal symbol*/ { + out->data[out->size++] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + ensureBits25(reader, 5); + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + out->size += length; + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + done = 1; /*end code, finish the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + if(out->allocsize - out->size < reserved_size) { + if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + if(max_output_size && out->size > max_output_size) { + ERROR_BREAK(109); /*error, larger than max size*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + /*out->data can be NULL (when LEN is zero), and arithmetics on NULL ptr is undefined*/ + if (LEN) { + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + } + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/ + ensureBits9(&reader, 3); + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ + if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; + if(error) break; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + if(error) { + /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && out->size > settings->max_output_size) error = 109; + } + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); + /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + unsigned error; + if(settings->custom_zlib) { + error = settings->custom_zlib(out, outsize, in, insize, settings); + if(error) { + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; + } + } else { + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + } + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->max_output_size = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifdef LODEPNG_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* LODEPNG_COMPILE_CRC */ +/*in this case, the function is only declared here, and must be defined externally +so that it will be linked in*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* LODEPNG_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(chunk); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + size_t available_size = (size_t)(end - chunk); + if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + if(total_chunk_length > available_size) return end; /*outside of range*/ + return chunk + total_chunk_length; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + size_t available_size = (size_t)(end - chunk); + if(chunk >= end || available_size < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + if(total_chunk_length > available_size) return end; /*outside of range*/ + return chunk + total_chunk_length; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = NULL; + dest->text_strings = NULL; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = NULL; + dest->itext_langtags = NULL; + dest->itext_transkeys = NULL; + dest->itext_strings = NULL; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + info->sbit_defined = 0; + info->sbit_r = info->sbit_g = info->sbit_b = info->sbit_a = 0; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +static unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); /*clears palette, keeps the above set colortype and bitdepth fields as-is*/ + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; + break; + } + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds + too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + recon[i + 3] = s3 + ((r3 + p3) >> 1u); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + } + } + for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); + } + break; + case 4: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version is also possible but that + adds too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i, ++j) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[j])); + } + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i != length; ++i, ++j) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[j]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_icc_size; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, &zlibsettings); + /*error: ICC profile larger than decoder->max_icc_size*/ + if(error && size > zlibsettings.max_output_size) error = 113; + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} + +/*significant bits chunk (sBIT)*/ +static unsigned readChunk_sBIT(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth; + if(info->color.colortype == LCT_GREY) { + /*error: this chunk must be 1 bytes for grayscale image*/ + if(chunkLength != 1) return 114; + if(data[0] == 0 || data[0] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/ + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 3 bytes for RGB and palette image*/ + if(chunkLength != 3) return 114; + if(data[0] == 0 || data[1] == 0 || data[2] == 0) return 115; + if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = data[0]; + info->sbit_g = data[1]; + info->sbit_b = data[2]; + } else if(info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 byte for grayscale with alpha image*/ + if(chunkLength != 2) return 114; + if(data[0] == 0 || data[1] == 0) return 115; + if(data[0] > bitdepth || data[1] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = info->sbit_g = info->sbit_b = data[0]; /*setting g and b is not required, but sensible*/ + info->sbit_a = data[1]; + } else if(info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 4 bytes for grayscale image*/ + if(chunkLength != 4) return 114; + if(data[0] == 0 || data[1] == 0 || data[2] == 0 || data[3] == 0) return 115; + if(data[0] > bitdepth || data[1] > bitdepth || data[2] > bitdepth || data[3] > bitdepth) return 115; + info->sbit_defined = 1; + info->sbit_r = data[0]; + info->sbit_g = data[1]; + info->sbit_b = data[2]; + info->sbit_a = data[3]; + } + + return 0; /* OK */ +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(chunkLength + 12 > insize - pos) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sBIT")) { + error = readChunk_sBIT(&state->info_png, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; /*points to beginning of next chunk*/ + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + size_t pos = (size_t)(chunk - in); + + /*error: next chunk out of bounds of the in buffer*/ + if(chunk < in || pos + 12 > insize) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the 12 bytes for length, chunk type and CRC*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if(pos + (size_t)chunkLength + 12 > insize || pos + (size_t)chunkLength + 12 < pos) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk (or int overflow)*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sBIT")) { + state->error = readChunk_sBIT(&state->info_png, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*disable reading things that this function doesn't output*/ + state.decoder.read_text_chunks = 0; + state.decoder.remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; + settings->max_text_size = 16777216; + settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + if(info->palettesize == 0 || info->palettesize > 256) { + return 68; /*invalid palette size, it is only allowed to be 1-256*/ + } + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_sBIT(ucvector* out, const LodePNGInfo* info) { + unsigned bitdepth = (info->color.colortype == LCT_PALETTE) ? 8 : info->color.bitdepth; + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY) { + if(info->sbit_r == 0 || info->sbit_r > bitdepth) return 115; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "sBIT")); + chunk[8] = info->sbit_r; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_PALETTE) { + if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0) return 115; + if(info->sbit_r > bitdepth || info->sbit_g > bitdepth || info->sbit_b > bitdepth) return 115; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 3, "sBIT")); + chunk[8] = info->sbit_r; + chunk[9] = info->sbit_g; + chunk[10] = info->sbit_b; + } else if(info->color.colortype == LCT_GREY_ALPHA) { + if(info->sbit_r == 0 || info->sbit_a == 0) return 115; + if(info->sbit_r > bitdepth || info->sbit_a > bitdepth) return 115; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "sBIT")); + chunk[8] = info->sbit_r; + chunk[9] = info->sbit_a; + } else if(info->color.colortype == LCT_RGBA) { + if(info->sbit_r == 0 || info->sbit_g == 0 || info->sbit_b == 0 || info->sbit_a == 0 || + info->sbit_r > bitdepth || info->sbit_g > bitdepth || + info->sbit_b > bitdepth || info->sbit_a > bitdepth) { + return 115; + } + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "sBIT")); + chunk[8] = info->sbit_r; + chunk[9] = info->sbit_g; + chunk[10] = info->sbit_b; + chunk[11] = info->sbit_a; + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (((size_t)1) << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + LodePNGColorMode auto_color; + + lodepng_info_init(&info); + lodepng_color_mode_init(&auto_color); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + /*this error is returned even if auto_convert is enabled and thus encoder could + generate the palette by itself: while allowing this could be possible in theory, + it may complicate the code or edge cases, and always requiring to give a palette + when setting this color type is a simpler contract*/ + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + unsigned allow_convert = 1; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, + info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&auto_color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->sbit_defined) { + /*if sbit is defined, due to strict requirements of which sbit values can be present for which color modes, + auto_convert can't be done in many cases. However, do support a few cases here. + TODO: more conversions may be possible, and it may also be possible to get a more appropriate color type out of + auto_choose_color if knowledge about sbit is used beforehand + */ + unsigned sbit_max = LODEPNG_MAX(LODEPNG_MAX(LODEPNG_MAX(info_png->sbit_r, info_png->sbit_g), + info_png->sbit_b), info_png->sbit_a); + unsigned equal = (!info_png->sbit_g || info_png->sbit_g == info_png->sbit_r) + && (!info_png->sbit_b || info_png->sbit_b == info_png->sbit_r) + && (!info_png->sbit_a || info_png->sbit_a == info_png->sbit_r); + allow_convert = 0; + if(info.color.colortype == LCT_PALETTE && + auto_color.colortype == LCT_PALETTE) { + /* input and output are palette, and in this case it may happen that palette data is + expected to be copied from info_raw into the info_png */ + allow_convert = 1; + } + /*going from 8-bit RGB to palette (or 16-bit as long as sbit_max <= 8) is possible + since both are 8-bit RGB for sBIT's purposes*/ + if(info.color.colortype == LCT_RGB && + auto_color.colortype == LCT_PALETTE && sbit_max <= 8) { + allow_convert = 1; + } + /*going from 8-bit RGBA to palette is also ok but only if sbit_a is exactly 8*/ + if(info.color.colortype == LCT_RGBA && auto_color.colortype == LCT_PALETTE && + info_png->sbit_a == 8 && sbit_max <= 8) { + allow_convert = 1; + } + /*going from 16-bit RGB(A) to 8-bit RGB(A) is ok if all sbit values are <= 8*/ + if((info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA) && info.color.bitdepth == 16 && + auto_color.colortype == info.color.colortype && auto_color.bitdepth == 8 && + sbit_max <= 8) { + allow_convert = 1; + } + /*going to less channels is ok if all bit values are equal (all possible values in sbit, + as well as the chosen bitdepth of the result). Due to how auto_convert works, + we already know that auto_color.colortype has less than or equal amount of channels than + info.colortype. Palette is not used here. This conversion is not allowed if + info_png->sbit_r < auto_color.bitdepth, because specifically for alpha, non-presence of + an sbit value heavily implies that alpha's bit depth is equal to the PNG bit depth (rather + than the bit depths set in the r, g and b sbit values, by how the PNG specification describes + handling tRNS chunk case with sBIT), so be conservative here about ignoring user input.*/ + if(info.color.colortype != LCT_PALETTE && auto_color.colortype != LCT_PALETTE && + equal && info_png->sbit_r == auto_color.bitdepth) { + allow_convert = 1; + } + } +#endif + if(state->encoder.force_palette) { + if(info.color.colortype != LCT_GREY && info.color.colortype != LCT_GREY_ALPHA && + (auto_color.colortype == LCT_GREY || auto_color.colortype == LCT_GREY_ALPHA)) { + /*user speficially forced a PLTE palette, so cannot convert to grayscale types because + the PNG specification only allows writing a suggested palette in PLTE for truecolor types*/ + allow_convert = 0; + } + } + if(allow_convert) { + lodepng_color_mode_copy(&info.color, &auto_color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } + if(info_png->sbit_defined) { + state->error = addChunk_sBIT(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + lodepng_color_mode_cleanup(&auto_color); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + /*this limit can be configured in LodePNGDecompressSettings*/ + case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; + case 110: return "custom zlib or inflate decompression failed"; + case 111: return "custom zlib or deflate compression failed"; + /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large text sizes.*/ + case 112: return "compressed text unreasonably large"; + /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large ICC profile*/ + case 113: return "ICC profile unreasonably large"; + case 114: return "sBIT chunk has wrong size for the color type of the image"; + case 115: return "sBIT value out of range"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +#ifdef LODEPNG_COMPILE_DECODER /**/ +unsigned load_file(std::vector& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} +#endif /**/ + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), buffer, &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), buffer, &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), buffer, &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/Quake/lodepng.hpp b/Quake/lodepng.hpp new file mode 100644 index 0000000..fdafc77 --- /dev/null +++ b/Quake/lodepng.hpp @@ -0,0 +1,2085 @@ +/* +LodePNG version 20220717 + +Copyright (c) 2005-2022 Lode Vandevenne + +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 LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc or clang. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +/*pass -DLODEPNG_NO_COMPILE_ZLIB to the compiler to disable this, or comment out LODEPNG_COMPILE_ZLIB below*/ +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +/*pass -DLODEPNG_NO_COMPILE_PNG to the compiler to disable this, or comment out LODEPNG_COMPILE_PNG below*/ +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +/*pass -DLODEPNG_NO_COMPILE_DECODER to the compiler to disable this, or comment out LODEPNG_COMPILE_DECODER below*/ +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +/*pass -DLODEPNG_NO_COMPILE_ENCODER to the compiler to disable this, or comment out LODEPNG_COMPILE_ENCODER below*/ +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +/*pass -DLODEPNG_NO_COMPILE_DISK to the compiler to disable this, or comment out LODEPNG_COMPILE_DISK below*/ +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +/*pass -DLODEPNG_NO_COMPILE_ANCILLARY_CHUNKS to the compiler to disable this, +or comment out LODEPNG_COMPILE_ANCILLARY_CHUNKS below*/ +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +/*pass -DLODEPNG_NO_COMPILE_ERROR_TEXT to the compiler to disable this, +or comment out LODEPNG_COMPILE_ERROR_TEXT below*/ +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +/*pass -DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler to disable the built-in ones, +or comment out LODEPNG_COMPILE_ALLOCATORS below*/ +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*Disable built-in CRC function, in that case a custom implementation of +lodepng_crc32 must be defined externally so that it can be linked in.*/ +#ifndef LODEPNG_NO_COMPILE_CRC +/*pass -DLODEPNG_NO_COMPILE_CRC to the compiler to disable the built-in one, +or comment out LODEPNG_COMPILE_CRC below*/ +#define LODEPNG_COMPILE_CRC +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +/*pass -DLODEPNG_NO_COMPILE_CPP to the compiler to disable C++ (not needed if a C-only compiler), +or comment out LODEPNG_COMPILE_CPP below*/ +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, + return an error, output a data size > max_output_size and all the data up to that point. This is + not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is + ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. + Set to 0 to impose no limit (the default).*/ + size_t max_output_size; + + /*use custom zlib decoder instead of built in one (default: null). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, with the default settings you can ignore this palette, since + LodePNG already fills the palette colors in the pixels of the raw RGBA output, + but when decoding to the original PNG color mode it is needed to reconstruct + the colors. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. The decoder will set them + equal to background_r, the encoder ignores them in this case. + + When decoding, you may get these in a different color mode than the one you requested + for the raw pixels: the colortype and bitdepth defined by info_png.color, that is the + ones defined in the header of the PNG image, are used. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + Non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including strings, keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long (plus the + additional null terminator). It's discouraged to use a single line length + longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + + Standard text chunk keywords and strings are encoded using Latin-1. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + International text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys", and the following text encodings are used: + keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. + keys must be 1-79 characters (plus the additional null terminator), the other + strings are any length. + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP, sBIT + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* + sBIT chunk: significant bits. Optional metadata, only set this if needed. + + If defined, these values give the bit depth of the original data. Since PNG only stores 1, 2, 4, 8 or 16-bit + per channel data, the significant bits value can be used to indicate the original encoded data has another + sample depth, such as 10 or 12. + + Encoders using this value, when storing the pixel data, should use the most significant bits + of the data to store the original bits, and use a good sample depth scaling method such as + "left bit replication" to fill in the least significant bits, rather than fill zeroes. + + Decoders using this value, if able to work with data that's e.g. 10-bit or 12-bit, should right + shift the data to go back to the original bit depth, but decoders are also allowed to ignore + sbit and work e.g. with the 8-bit or 16-bit data from the PNG directly, since thanks + to the encoder contract, the values encoded in PNG are in valid range for the PNG bit depth. + + For grayscale images, sbit_g and sbit_b are not used, and for images that don't use color + type RGBA or grayscale+alpha, sbit_a is not used (it's not used even for palette images with + translucent palette values, or images with color key). The values that are used must be + greater than zero and smaller than or equal to the PNG bit depth. + + The color type from the header in the PNG image defines these used and unused fields: if + decoding with a color mode conversion, such as always decoding to RGBA, this metadata still + only uses the color type of the original PNG, and may e.g. lack the alpha channel info + if the PNG was RGB. When encoding with auto_convert (as well as without), also always the + color model defined in info_png.color determines this. + + NOTE: enabling sbit can hurt compression, because the encoder can then not always use + auto_convert to choose a more optimal color mode for the data, because the PNG format has + strict requirements for the allowed sbit values in combination with color modes. + For example, setting these fields to 10-bit will force the encoder to keep using a 16-bit per channel + color mode, even if the pixel data would in fact fit in a more efficient 8-bit mode. + */ + unsigned sbit_defined; /*is significant bits given? if not, the values below are unused*/ + unsigned sbit_r; /*red or gray component of significant bits*/ + unsigned sbit_g; /*green component of significant bits*/ + unsigned sbit_b; /*blue component of significant bits*/ + unsigned sbit_a; /*alpha component of significant bits*/ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; + + /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, + unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. + By default it is a value that prevents unreasonably large strings from hogging memory. */ + size_t max_text_size; + + /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to + 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any + legitimate profile could be to hog memory. */ + size_t max_icc_size; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is always created. If color type is explicitely set + to a grayscale type (1 or 4), this is not done and is ignored. If enabling this, + a palette must be present in the info_png. + NOTE: enabling this may worsen compression if auto_convert is used to choose + optimal color mode, because it cannot use grayscale color modes in this case*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR, which is handled by lodepng_inspect) +of the PNG file and outputs what it read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + sBIT: significant bits + +1.2. features not supported +--------------------------- + +The following features are not (yet) supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The hIST and sPLT public chunks are not (yet) supported but treated as unknown chunks + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + +It is not recommended to use the numerical values to programmatically make +different decisions based on error types as the numbers are not guaranteed to +stay backwards compatible. They are for human consumption only. Programmatically +only 0 or non-0 matter. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +NOTE: these examples do not support wide-character filenames, you can use an +external method to handle such files and encode or decode in-memory + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 13 jun 2022: added support for the sBIT chunk. +*) 09 jan 2022: minor decoder speed improvements. +*) 27 jun 2021: added warnings that file reading/writing functions don't support + wide-character filenames (support for this is not planned, opening files is + not the core part of PNG decoding/decoding and is platform dependent). +*) 17 okt 2020: prevent decoding too large text/icc chunks by default. +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2022 Lode Vandevenne +*/ diff --git a/Quake/main_sdl.cpp b/Quake/main_sdl.cpp new file mode 100644 index 0000000..a530949 --- /dev/null +++ b/Quake/main_sdl.cpp @@ -0,0 +1,157 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif +#include + +static void Sys_AtExit (void) +{ + SDL_Quit(); +} + +static void Sys_InitSDL (void) +{ +#if defined(USE_SDL2) + SDL_version v; + SDL_version *sdl_version = &v; + SDL_GetVersion(&v); +#else + const SDL_version *sdl_version = SDL_Linked_Version(); +#endif + + Sys_Printf("Found SDL version %i.%i.%i\n",sdl_version->major,sdl_version->minor,sdl_version->patch); + + if (SDL_Init(0) < 0) { + Sys_Error("Couldn't init SDL: %s", SDL_GetError()); + } + atexit(Sys_AtExit); +} + +#define DEFAULT_MEMORY (256 * 1024 * 1024) // ericw -- was 72MB (64-bit) / 64MB (32-bit) + +static quakeparms_t parms; + +// On OS X we call SDL_main from the launcher, but SDL2 doesn't redefine main +// as SDL_main on OS X anymore, so we do it ourselves. +#if defined(USE_SDL2) && defined(__APPLE__) +#define main SDL_main +#endif + +int main(int argc, char *argv[]) +{ + int t; + double time, oldtime, newtime; + + host_parms = &parms; + parms.basedir = "."; + + parms.argc = argc; + parms.argv = argv; + + parms.errstate = 0; + + COM_InitArgv(parms.argc, parms.argv); + + isDedicated = (COM_CheckParm("-dedicated") != 0); + + Sys_InitSDL (); + + Sys_Init(); + + Sys_Printf("Initializing QuakeSpasm v%s\n", QUAKESPASM_VER_STRING); + + parms.memsize = DEFAULT_MEMORY; + if (COM_CheckParm("-heapsize")) + { + t = COM_CheckParm("-heapsize") + 1; + if (t < com_argc) + parms.memsize = Q_atoi(com_argv[t]) * 1024; + } + + parms.membase = malloc (parms.memsize); + + if (!parms.membase) + Sys_Error ("Not enough memory free; check disk space\n"); + + Sys_Printf("Host_Init\n"); + Host_Init(); + + oldtime = Sys_DoubleTime(); + if (isDedicated) + { + while (1) + { + newtime = Sys_DoubleTime (); + time = newtime - oldtime; + + while (time < sys_ticrate.value ) + { + SDL_Delay(1); + newtime = Sys_DoubleTime (); + time = newtime - oldtime; + } + + Host_Frame (time); + oldtime = newtime; + } + } + else + while (1) + { + /* If we have no input focus at all, sleep a bit */ + if (!VID_HasMouseOrInputFocus() || cl.paused) + { + SDL_Delay(16); + } + /* If we're minimised, sleep a bit more */ + if (VID_IsMinimized()) + { + scr_skipupdate = 1; + SDL_Delay(32); + } + else + { + scr_skipupdate = 0; + } + newtime = Sys_DoubleTime (); + time = newtime - oldtime; + + Host_Frame (time); + + if (time < sys_throttle.value && !cls.timedemo) + SDL_Delay(1); + + oldtime = newtime; + } + + return 0; +} diff --git a/Quake/mathlib.cpp b/Quake/mathlib.cpp new file mode 100644 index 0000000..7259f15 --- /dev/null +++ b/Quake/mathlib.cpp @@ -0,0 +1,474 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// mathlib.c -- math primitives + +#include "quakedef.hpp" + +vec3_t vec3_origin = {0,0,0}; + +/*-----------------------------------------------------------------*/ + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + +//johnfitz -- removed RotatePointAroundVector() becuase it's no longer used and my compiler fucked it up anyway + +/*-----------------------------------------------------------------*/ + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, mplane_t *p) +{ + float dist1, dist2; + int xneg, yneg, zneg; + int sides; + +#if 0 // this is done by the BOX_ON_PLANE_SIDE macro before calling this + // function +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } +#endif + + xneg = p->signbits & 1; + yneg = (p->signbits >> 1) & 1; + zneg = (p->signbits >> 2) & 1; + + dist1 = p->normal[0] * (xneg ? emins : emaxs)[0] + + p->normal[1] * (yneg ? emins : emaxs)[1] + + p->normal[2] * (zneg ? emins : emaxs)[2]; + dist2 = p->normal[0] * (xneg ? emaxs : emins)[0] + + p->normal[1] * (yneg ? emaxs : emins)[1] + + p->normal[2] * (zneg ? emaxs : emins)[2]; + + if (p->signbits & ~7) + Sys_Error ("BoxOnPlaneSide: Bad signbits"); + +#if 0 + int i; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; +#endif + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + +#ifdef PARANOID + if (sides == 0) + Sys_Error ("BoxOnPlaneSide: sides==0"); +#endif + + return sides; +} + +//johnfitz -- the opposite of AngleVectors. this takes forward and generates pitch yaw roll +//TODO: take right and up vectors to properly set yaw and roll +void VectorAngles (const vec3_t forward, vec3_t angles) +{ + vec3_t temp; + + temp[0] = forward[0]; + temp[1] = forward[1]; + temp[2] = 0; + angles[PITCH] = -atan2(forward[2], VectorLength(temp)) / M_PI_DIV_180; + angles[YAW] = atan2(forward[1], forward[0]) / M_PI_DIV_180; + angles[ROLL] = 0; +} + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; +} + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +vec_t VectorLength(vec3_t v) +{ + return sqrt(DotProduct(v,v)); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = sqrt(DotProduct(v,v)); + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +/* +=================== +FloorDivMod + +Returns mathematically correct (floor-based) quotient and remainder for +numer and denom, both of which should contain no fractional part. The +quotient must fit in 32 bits. +==================== +*/ + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem) +{ + int q, r; + double x; + +#ifndef PARANOID + if (denom <= 0.0) + Sys_Error ("FloorDivMod: bad denominator %f\n", denom); + +// if ((floor(numer) != numer) || (floor(denom) != denom)) +// Sys_Error ("FloorDivMod: non-integer numer or denom %f %f\n", +// numer, denom); +#endif + + if (numer >= 0.0) + { + + x = floor(numer / denom); + q = (int)x; + r = (int)floor(numer - (x * denom)); + } + else + { + // + // perform operations with positive values, and fix mod to make floor-based + // + x = floor(-numer / denom); + q = -(int)x; + r = (int)floor(-numer - (x * denom)); + if (r != 0) + { + q--; + r = (int)denom - r; + } + } + + *quotient = q; + *rem = r; +} + + +/* +=================== +GreatestCommonDivisor +==================== +*/ +int GreatestCommonDivisor (int i1, int i2) +{ + if (i1 > i2) + { + if (i2 == 0) + return (i1); + return GreatestCommonDivisor (i2, i1 % i2); + } + else + { + if (i1 == 0) + return (i2); + return GreatestCommonDivisor (i1, i2 % i1); + } +} + + +/* +=================== +Invert24To16 + +Inverts an 8.24 value to a 16.16 value +==================== +*/ + +fixed16_t Invert24To16(fixed16_t val) +{ + if (val < 256) + return (0xFFFFFFFF); + + return (fixed16_t) + (((double)0x10000 * (double)0x1000000 / (double)val) + 0.5); +} + diff --git a/Quake/mathlib.hpp b/Quake/mathlib.hpp new file mode 100644 index 0000000..2f5c679 --- /dev/null +++ b/Quake/mathlib.hpp @@ -0,0 +1,123 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __MATHLIB_H +#define __MATHLIB_H + +// mathlib.h + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#define M_PI_DIV_180 (M_PI / 180.0) //johnfitz +#define DEG2RAD(a) ((a) * M_PI_DIV_180) + +struct mplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255 << 23) /* 7F800000 */ +#if 0 /* macro is violating strict aliasing rules */ +#define IS_NAN(x) (((*(int *) (char *) &x) & nanmask) == nanmask) +#else +static inline int IS_NAN (float x) { + union { float f; int i; } num; + num.f = x; + return ((num.i & nanmask) == nanmask); +} +#endif + +#define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5)) //johnfitz -- from joequake + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define DoublePrecisionDotProduct(x,y) ((double)(x)[0]*(y)[0]+(double)(x)[1]*(y)[1]+(double)(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];} + +//johnfitz -- courtesy of lordhavoc +// QuakeSpasm: To avoid strict aliasing violations, use a float/int union instead of type punning. +#define VectorNormalizeFast(_v)\ +{\ + union { float f; int i; } _y, _number;\ + _number.f = DotProduct((_v), (_v));\ + if (_number.f != 0.0)\ + {\ + _y.i = 0x5f3759df - (_number.i >> 1);\ + _y.f = _y.f * (1.5f - (_number.f * 0.5f * _y.f * _y.f));\ + VectorScale((_v), _y.f, (_v));\ + }\ +} + +void TurnVector (vec3_t out, const vec3_t forward, const vec3_t side, float angle); //johnfitz +void VectorAngles (const vec3_t forward, vec3_t angles); //johnfitz + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +float VectorNormalize (vec3_t v); // returns vector length +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void FloorDivMod (double numer, double denom, int *quotient, + int *rem); +fixed16_t Invert24To16(fixed16_t val); +int GreatestCommonDivisor (int i1, int i2); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); +float anglemod(float a); + + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +#endif /* __MATHLIB_H */ + diff --git a/Quake/menu.cpp b/Quake/menu.cpp new file mode 100644 index 0000000..0d746e7 --- /dev/null +++ b/Quake/menu.cpp @@ -0,0 +1,2794 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" +#include "bgmusic.hpp" + +void (*vid_menucmdfn)(void); //johnfitz +void (*vid_menudrawfn)(void); +void (*vid_menukeyfn)(int key); + +enum m_state_e m_state; + +void M_Menu_Main_f (void); + void M_Menu_SinglePlayer_f (void); + void M_Menu_Load_f (void); + void M_Menu_Save_f (void); + void M_Menu_MultiPlayer_f (void); + void M_Menu_Setup_f (void); + void M_Menu_Net_f (void); + void M_Menu_LanConfig_f (void); + void M_Menu_GameOptions_f (void); + void M_Menu_Search_f (void); + void M_Menu_ServerList_f (void); + void M_Menu_Options_f (void); + void M_Menu_Keys_f (void); + void M_Menu_Video_f (void); + void M_Menu_Help_f (void); + void M_Menu_Quit_f (void); + +void M_Main_Draw (void); + void M_SinglePlayer_Draw (void); + void M_Load_Draw (void); + void M_Save_Draw (void); + void M_MultiPlayer_Draw (void); + void M_Setup_Draw (void); + void M_Net_Draw (void); + void M_LanConfig_Draw (void); + void M_GameOptions_Draw (void); + void M_Search_Draw (void); + void M_ServerList_Draw (void); + void M_Options_Draw (void); + void M_Keys_Draw (void); + void M_Video_Draw (void); + void M_Help_Draw (void); + void M_Quit_Draw (void); + +void M_Main_Key (int key); + void M_SinglePlayer_Key (int key); + void M_Load_Key (int key); + void M_Save_Key (int key); + void M_MultiPlayer_Key (int key); + void M_Setup_Key (int key); + void M_Net_Key (int key); + void M_LanConfig_Key (int key); + void M_GameOptions_Key (int key); + void M_Search_Key (int key); + void M_ServerList_Key (int key); + void M_Options_Key (int key); + void M_Keys_Key (int key); + void M_Video_Key (int key); + void M_Help_Key (int key); + void M_Quit_Key (int key); + +qboolean m_entersound; // play after drawing a frame, so caching + // won't disrupt the sound +qboolean m_recursiveDraw; + +enum m_state_e m_return_state; +qboolean m_return_onerror; +char m_return_reason [32]; + +#define StartingGame (m_multiplayer_cursor == 1) +#define JoiningGame (m_multiplayer_cursor == 0) +#define IPXConfig (m_net_cursor == 0) +#define TCPIPConfig (m_net_cursor == 1) + +void M_ConfigureNetSubsystem(void); + +/* +================ +M_DrawCharacter + +Draws one solid graphics character +================ +*/ +void M_DrawCharacter (int cx, int line, int num) +{ + Draw_Character (cx, line, num); +} + +void M_Print (int cx, int cy, const char *str) +{ + while (*str) + { + M_DrawCharacter (cx, cy, (*str)+128); + str++; + cx += 8; + } +} + +void M_PrintWhite (int cx, int cy, const char *str) +{ + while (*str) + { + M_DrawCharacter (cx, cy, *str); + str++; + cx += 8; + } +} + +void M_DrawTransPic (int x, int y, qpic_t *pic) +{ + Draw_Pic (x, y, pic); //johnfitz -- simplified becuase centering is handled elsewhere +} + +void M_DrawPic (int x, int y, qpic_t *pic) +{ + Draw_Pic (x, y, pic); //johnfitz -- simplified becuase centering is handled elsewhere +} + +void M_DrawTransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom) //johnfitz -- more parameters +{ + Draw_TransPicTranslate (x, y, pic, top, bottom); //johnfitz -- simplified becuase centering is handled elsewhere +} + +void M_DrawTextBox (int x, int y, int width, int lines) +{ + qpic_t *p; + int cx, cy; + int n; + + // draw left side + cx = x; + cy = y; + p = Draw_CachePic ("gfx/box_tl.lmp"); + M_DrawTransPic (cx, cy, p); + p = Draw_CachePic ("gfx/box_ml.lmp"); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawTransPic (cx, cy, p); + } + p = Draw_CachePic ("gfx/box_bl.lmp"); + M_DrawTransPic (cx, cy+8, p); + + // draw middle + cx += 8; + while (width > 0) + { + cy = y; + p = Draw_CachePic ("gfx/box_tm.lmp"); + M_DrawTransPic (cx, cy, p); + p = Draw_CachePic ("gfx/box_mm.lmp"); + for (n = 0; n < lines; n++) + { + cy += 8; + if (n == 1) + p = Draw_CachePic ("gfx/box_mm2.lmp"); + M_DrawTransPic (cx, cy, p); + } + p = Draw_CachePic ("gfx/box_bm.lmp"); + M_DrawTransPic (cx, cy+8, p); + width -= 2; + cx += 16; + } + + // draw right side + cy = y; + p = Draw_CachePic ("gfx/box_tr.lmp"); + M_DrawTransPic (cx, cy, p); + p = Draw_CachePic ("gfx/box_mr.lmp"); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawTransPic (cx, cy, p); + } + p = Draw_CachePic ("gfx/box_br.lmp"); + M_DrawTransPic (cx, cy+8, p); +} + +//============================================================================= + +int m_save_demonum; + +/* +================ +M_ToggleMenu_f +================ +*/ +void M_ToggleMenu_f (void) +{ + m_entersound = true; + + if (key_dest == key_menu) + { + if (m_state != m_main) + { + M_Menu_Main_f (); + return; + } + + IN_Activate(); + key_dest = key_game; + m_state = m_none; + return; + } + if (key_dest == key_console) + { + Con_ToggleConsole_f (); + } + else + { + M_Menu_Main_f (); + } +} + + +//============================================================================= +/* MAIN MENU */ + +int m_main_cursor; +#define MAIN_ITEMS 5 + + +void M_Menu_Main_f (void) +{ + if (key_dest != key_menu) + { + m_save_demonum = cls.demonum; + cls.demonum = -1; + } + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_main; + m_entersound = true; +} + + +void M_Main_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/ttl_main.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + M_DrawTransPic (72, 32, Draw_CachePic ("gfx/mainmenu.lmp") ); + + f = (int)(realtime * 10)%6; + + M_DrawTransPic (54, 32 + m_main_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); +} + + +void M_Main_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + IN_Activate(); + key_dest = key_game; + m_state = m_none; + cls.demonum = m_save_demonum; + if (!fitzmode && !cl_startdemos.value) /* QuakeSpasm customization: */ + break; + if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected) + CL_NextDemo (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_main_cursor >= MAIN_ITEMS) + m_main_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_main_cursor < 0) + m_main_cursor = MAIN_ITEMS - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + + switch (m_main_cursor) + { + case 0: + M_Menu_SinglePlayer_f (); + break; + + case 1: + M_Menu_MultiPlayer_f (); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Help_f (); + break; + + case 4: + M_Menu_Quit_f (); + break; + } + } +} + +//============================================================================= +/* SINGLE PLAYER MENU */ + +int m_singleplayer_cursor; +#define SINGLEPLAYER_ITEMS 3 + + +void M_Menu_SinglePlayer_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_singleplayer; + m_entersound = true; +} + + +void M_SinglePlayer_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/ttl_sgl.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + M_DrawTransPic (72, 32, Draw_CachePic ("gfx/sp_menu.lmp") ); + + f = (int)(realtime * 10)%6; + + M_DrawTransPic (54, 32 + m_singleplayer_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); +} + + +void M_SinglePlayer_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS) + m_singleplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_singleplayer_cursor < 0) + m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + + switch (m_singleplayer_cursor) + { + case 0: + if (sv.active) + if (!SCR_ModalMessage("Are you sure you want to\nstart a new game? (y/n)\n", 0.0f)) + break; + IN_Activate(); + key_dest = key_game; + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("maxplayers 1\n"); + Cbuf_AddText ("deathmatch 0\n"); //johnfitz + Cbuf_AddText ("coop 0\n"); //johnfitz + Cbuf_AddText ("map start\n"); + break; + + case 1: + M_Menu_Load_f (); + break; + + case 2: + M_Menu_Save_f (); + break; + } + } +} + +//============================================================================= +/* LOAD/SAVE MENU */ + +int load_cursor; // 0 < load_cursor < MAX_SAVEGAMES + +#define MAX_SAVEGAMES 20 /* johnfitz -- increased from 12 */ +char m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1]; +int loadable[MAX_SAVEGAMES]; + +void M_ScanSaves (void) +{ + int i, j; + char name[MAX_OSPATH]; + FILE *f; + int version; + + for (i = 0; i < MAX_SAVEGAMES; i++) + { + strcpy (m_filenames[i], "--- UNUSED SLOT ---"); + loadable[i] = false; + q_snprintf (name, sizeof(name), "%s/s%i.sav", com_gamedir, i); + f = fopen (name, "r"); + if (!f) { + continue; + } + if (fscanf(f, "%i\n", &version) != 1 || + fscanf(f, "%79s\n", name) != 1) { + fclose(f); + continue; + } + q_strlcpy (m_filenames[i], name, SAVEGAME_COMMENT_LENGTH+1); + + // change _ back to space + for (j = 0; j < SAVEGAME_COMMENT_LENGTH; j++) + { + if (m_filenames[i][j] == '_') + m_filenames[i][j] = ' '; + } + loadable[i] = true; + fclose (f); + } +} + +void M_Menu_Load_f (void) +{ + m_entersound = true; + m_state = m_load; + + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + M_ScanSaves (); +} + + +void M_Menu_Save_f (void) +{ + if (!sv.active) + return; + if (cl.intermission) + return; + if (svs.maxclients != 1) + return; + m_entersound = true; + m_state = m_save; + + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + M_ScanSaves (); +} + + +void M_Load_Draw (void) +{ + int i; + qpic_t *p; + + p = Draw_CachePic ("gfx/p_load.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + for (i = 0; i < MAX_SAVEGAMES; i++) + M_Print (16, 32 + 8*i, m_filenames[i]); + +// line cursor + M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Save_Draw (void) +{ + int i; + qpic_t *p; + + p = Draw_CachePic ("gfx/p_save.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + for (i = 0; i < MAX_SAVEGAMES; i++) + M_Print (16, 32 + 8*i, m_filenames[i]); + +// line cursor + M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Load_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_SinglePlayer_f (); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + S_LocalSound ("misc/menu2.wav"); + if (!loadable[load_cursor]) + return; + m_state = m_none; + key_dest = key_game; + + // Host_Loadgame_f can't bring up the loading plaque because too much + // stack space has been used, so do it now + SCR_BeginLoadingPlaque (); + + // issue the load command + Cbuf_AddText (va ("load s%i\n", load_cursor) ); + return; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor--; + if (load_cursor < 0) + load_cursor = MAX_SAVEGAMES-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor++; + if (load_cursor >= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + + +void M_Save_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_SinglePlayer_f (); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_state = m_none; + IN_Activate(); + key_dest = key_game; + Cbuf_AddText (va("save s%i\n", load_cursor)); + return; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor--; + if (load_cursor < 0) + load_cursor = MAX_SAVEGAMES-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + load_cursor++; + if (load_cursor >= MAX_SAVEGAMES) + load_cursor = 0; + break; + } +} + +//============================================================================= +/* MULTIPLAYER MENU */ + +int m_multiplayer_cursor; +#define MULTIPLAYER_ITEMS 3 + + +void M_Menu_MultiPlayer_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_multiplayer; + m_entersound = true; +} + + +void M_MultiPlayer_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + M_DrawTransPic (72, 32, Draw_CachePic ("gfx/mp_menu.lmp") ); + + f = (int)(realtime * 10)%6; + + M_DrawTransPic (54, 32 + m_multiplayer_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); + + if (ipxAvailable || tcpipAvailable) + return; + M_PrintWhite ((320/2) - ((27*8)/2), 148, "No Communications Available"); +} + + +void M_MultiPlayer_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS) + m_multiplayer_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_multiplayer_cursor < 0) + m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + switch (m_multiplayer_cursor) + { + case 0: + if (ipxAvailable || tcpipAvailable) + M_Menu_Net_f (); + break; + + case 1: + if (ipxAvailable || tcpipAvailable) + M_Menu_Net_f (); + break; + + case 2: + M_Menu_Setup_f (); + break; + } + } +} + +//============================================================================= +/* SETUP MENU */ + +int setup_cursor = 4; +int setup_cursor_table[] = {40, 56, 80, 104, 140}; + +char setup_hostname[16]; +char setup_myname[16]; +int setup_oldtop; +int setup_oldbottom; +int setup_top; +int setup_bottom; + +#define NUM_SETUP_CMDS 5 + +void M_Menu_Setup_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_setup; + m_entersound = true; + Q_strcpy(setup_myname, cl_name.string); + Q_strcpy(setup_hostname, hostname.string); + setup_top = setup_oldtop = ((int)cl_color.value) >> 4; + setup_bottom = setup_oldbottom = ((int)cl_color.value) & 15; +} + + +void M_Setup_Draw (void) +{ + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + M_Print (64, 40, "Hostname"); + M_DrawTextBox (160, 32, 16, 1); + M_Print (168, 40, setup_hostname); + + M_Print (64, 56, "Your name"); + M_DrawTextBox (160, 48, 16, 1); + M_Print (168, 56, setup_myname); + + M_Print (64, 80, "Shirt color"); + M_Print (64, 104, "Pants color"); + + M_DrawTextBox (64, 140-8, 14, 1); + M_Print (72, 140, "Accept Changes"); + + p = Draw_CachePic ("gfx/bigbox.lmp"); + M_DrawTransPic (160, 64, p); + p = Draw_CachePic ("gfx/menuplyr.lmp"); + M_DrawTransPicTranslate (172, 72, p, setup_top, setup_bottom); + + M_DrawCharacter (56, setup_cursor_table [setup_cursor], 12+((int)(realtime*4)&1)); + + if (setup_cursor == 0) + M_DrawCharacter (168 + 8*strlen(setup_hostname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); + + if (setup_cursor == 1) + M_DrawCharacter (168 + 8*strlen(setup_myname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); +} + + +void M_Setup_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_MultiPlayer_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + setup_cursor--; + if (setup_cursor < 0) + setup_cursor = NUM_SETUP_CMDS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + setup_cursor++; + if (setup_cursor >= NUM_SETUP_CMDS) + setup_cursor = 0; + break; + + case K_LEFTARROW: + if (setup_cursor < 2) + return; + S_LocalSound ("misc/menu3.wav"); + if (setup_cursor == 2) + setup_top = setup_top - 1; + if (setup_cursor == 3) + setup_bottom = setup_bottom - 1; + break; + case K_RIGHTARROW: + if (setup_cursor < 2) + return; +forward: + S_LocalSound ("misc/menu3.wav"); + if (setup_cursor == 2) + setup_top = setup_top + 1; + if (setup_cursor == 3) + setup_bottom = setup_bottom + 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + if (setup_cursor == 0 || setup_cursor == 1) + return; + + if (setup_cursor == 2 || setup_cursor == 3) + goto forward; + + // setup_cursor == 4 (OK) + if (Q_strcmp(cl_name.string, setup_myname) != 0) + Cbuf_AddText ( va ("name \"%s\"\n", setup_myname) ); + if (Q_strcmp(hostname.string, setup_hostname) != 0) + Cvar_Set("hostname", setup_hostname); + if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom) + Cbuf_AddText( va ("color %i %i\n", setup_top, setup_bottom) ); + m_entersound = true; + M_Menu_MultiPlayer_f (); + break; + + case K_BACKSPACE: + if (setup_cursor == 0) + { + if (strlen(setup_hostname)) + setup_hostname[strlen(setup_hostname)-1] = 0; + } + + if (setup_cursor == 1) + { + if (strlen(setup_myname)) + setup_myname[strlen(setup_myname)-1] = 0; + } + break; + } + + if (setup_top > 13) + setup_top = 0; + if (setup_top < 0) + setup_top = 13; + if (setup_bottom > 13) + setup_bottom = 0; + if (setup_bottom < 0) + setup_bottom = 13; +} + + +void M_Setup_Char (int k) +{ + int l; + + switch (setup_cursor) + { + case 0: + l = strlen(setup_hostname); + if (l < 15) + { + setup_hostname[l+1] = 0; + setup_hostname[l] = k; + } + break; + case 1: + l = strlen(setup_myname); + if (l < 15) + { + setup_myname[l+1] = 0; + setup_myname[l] = k; + } + break; + } +} + + +qboolean M_Setup_TextEntry (void) +{ + return (setup_cursor == 0 || setup_cursor == 1); +} + +//============================================================================= +/* NET MENU */ + +int m_net_cursor; +int m_net_items; + +const char *net_helpMessage [] = +{ +/* .........1.........2.... */ + " Novell network LANs ", + " or Windows 95 DOS-box. ", + " ", + "(LAN=Local Area Network)", + + " Commonly used to play ", + " over the Internet, but ", + " also used on a Local ", + " Area Network. " +}; + +void M_Menu_Net_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_net; + m_entersound = true; + m_net_items = 2; + + if (m_net_cursor >= m_net_items) + m_net_cursor = 0; + m_net_cursor--; + M_Net_Key (K_DOWNARROW); +} + + +void M_Net_Draw (void) +{ + int f; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + f = 32; + + if (ipxAvailable) + p = Draw_CachePic ("gfx/netmen3.lmp"); + else + p = Draw_CachePic ("gfx/dim_ipx.lmp"); + M_DrawTransPic (72, f, p); + + f += 19; + if (tcpipAvailable) + p = Draw_CachePic ("gfx/netmen4.lmp"); + else + p = Draw_CachePic ("gfx/dim_tcp.lmp"); + M_DrawTransPic (72, f, p); + + f = (320-26*8)/2; + M_DrawTextBox (f, 96, 24, 4); + f += 8; + M_Print (f, 104, net_helpMessage[m_net_cursor*4+0]); + M_Print (f, 112, net_helpMessage[m_net_cursor*4+1]); + M_Print (f, 120, net_helpMessage[m_net_cursor*4+2]); + M_Print (f, 128, net_helpMessage[m_net_cursor*4+3]); + + f = (int)(realtime * 10)%6; + M_DrawTransPic (54, 32 + m_net_cursor * 20,Draw_CachePic( va("gfx/menudot%i.lmp", f+1 ) ) ); +} + + +void M_Net_Key (int k) +{ +again: + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_MultiPlayer_f (); + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + if (++m_net_cursor >= m_net_items) + m_net_cursor = 0; + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + if (--m_net_cursor < 0) + m_net_cursor = m_net_items - 1; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + M_Menu_LanConfig_f (); + break; + } + + if (m_net_cursor == 0 && !ipxAvailable) + goto again; + if (m_net_cursor == 1 && !tcpipAvailable) + goto again; +} + +//============================================================================= +/* OPTIONS MENU */ + +enum +{ + OPT_CUSTOMIZE = 0, + OPT_CONSOLE, // 1 + OPT_DEFAULTS, // 2 + OPT_SCALE, + OPT_SCRSIZE, + OPT_GAMMA, + OPT_CONTRAST, + OPT_MOUSESPEED, + OPT_SBALPHA, + OPT_SNDVOL, + OPT_MUSICVOL, + OPT_MUSICEXT, + OPT_ALWAYRUN, + OPT_INVMOUSE, + OPT_ALWAYSMLOOK, + OPT_LOOKSPRING, + OPT_LOOKSTRAFE, +//#ifdef _WIN32 +// OPT_USEMOUSE, +//#endif + OPT_VIDEO, // This is the last before OPTIONS_ITEMS + OPTIONS_ITEMS +}; + +enum +{ + ALWAYSRUN_OFF = 0, + ALWAYSRUN_VANILLA, + ALWAYSRUN_QUAKESPASM, + ALWAYSRUN_ITEMS +}; + +#define SLIDER_RANGE 10 + +int options_cursor; + +void M_Menu_Options_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_options; + m_entersound = true; +} + + +void M_AdjustSliders (int dir) +{ + int curr_alwaysrun, target_alwaysrun; + float f, l; + + S_LocalSound ("misc/menu3.wav"); + + switch (options_cursor) + { + case OPT_SCALE: // console and menu scale + l = ((vid.width + 31) / 32) / 10.0; + f = scr_conscale.value + dir * .1; + if (f < 1) f = 1; + else if(f > l) f = l; + Cvar_SetValue ("scr_conscale", f); + Cvar_SetValue ("scr_menuscale", f); + Cvar_SetValue ("scr_sbarscale", f); + break; + case OPT_SCRSIZE: // screen size + f = scr_viewsize.value + dir * 10; + if (f > 120) f = 120; + else if(f < 30) f = 30; + Cvar_SetValue ("viewsize", f); + break; + case OPT_GAMMA: // gamma + f = vid_gamma.value - dir * 0.05; + if (f < 0.5) f = 0.5; + else if (f > 1) f = 1; + Cvar_SetValue ("gamma", f); + break; + case OPT_CONTRAST: // contrast + f = vid_contrast.value + dir * 0.1; + if (f < 1) f = 1; + else if (f > 2) f = 2; + Cvar_SetValue ("contrast", f); + break; + case OPT_MOUSESPEED: // mouse speed + f = sensitivity.value + dir * 0.5; + if (f > 11) f = 11; + else if (f < 1) f = 1; + Cvar_SetValue ("sensitivity", f); + break; + case OPT_SBALPHA: // statusbar alpha + f = scr_sbaralpha.value - dir * 0.05; + if (f < 0) f = 0; + else if (f > 1) f = 1; + Cvar_SetValue ("scr_sbaralpha", f); + break; + case OPT_MUSICVOL: // music volume + f = bgmvolume.value + dir * 0.1; + if (f < 0) f = 0; + else if (f > 1) f = 1; + Cvar_SetValue ("bgmvolume", f); + break; + case OPT_MUSICEXT: // enable external music vs cdaudio + Cvar_Set ("bgm_extmusic", bgm_extmusic.value ? "0" : "1"); + break; + case OPT_SNDVOL: // sfx volume + f = sfxvolume.value + dir * 0.1; + if (f < 0) f = 0; + else if (f > 1) f = 1; + Cvar_SetValue ("volume", f); + break; + + case OPT_ALWAYRUN: // always run + if (cl_alwaysrun.value) + curr_alwaysrun = ALWAYSRUN_QUAKESPASM; + else if (cl_forwardspeed.value > 200) + curr_alwaysrun = ALWAYSRUN_VANILLA; + else + curr_alwaysrun = ALWAYSRUN_OFF; + + target_alwaysrun = (ALWAYSRUN_ITEMS + curr_alwaysrun + dir) % ALWAYSRUN_ITEMS; + + if (target_alwaysrun == ALWAYSRUN_VANILLA) + { + Cvar_SetValue ("cl_alwaysrun", 0); + Cvar_SetValue ("cl_forwardspeed", 400); + Cvar_SetValue ("cl_backspeed", 400); + } + else if (target_alwaysrun == ALWAYSRUN_QUAKESPASM) + { + Cvar_SetValue ("cl_alwaysrun", 1); + Cvar_SetValue ("cl_forwardspeed", 200); + Cvar_SetValue ("cl_backspeed", 200); + } + else // ALWAYSRUN_OFF + { + Cvar_SetValue ("cl_alwaysrun", 0); + Cvar_SetValue ("cl_forwardspeed", 200); + Cvar_SetValue ("cl_backspeed", 200); + } + break; + + case OPT_INVMOUSE: // invert mouse + Cvar_SetValue ("m_pitch", -m_pitch.value); + break; + + case OPT_ALWAYSMLOOK: + if (in_mlook.state & 1) + Cbuf_AddText("-mlook"); + else + Cbuf_AddText("+mlook"); + break; + + case OPT_LOOKSPRING: // lookspring + Cvar_Set ("lookspring", lookspring.value ? "0" : "1"); + break; + + case OPT_LOOKSTRAFE: // lookstrafe + Cvar_Set ("lookstrafe", lookstrafe.value ? "0" : "1"); + break; + } +} + + +void M_DrawSlider (int x, int y, float range, float value, const char *format) +{ + int i; + char buffer[5]; + + if (range < 0) + range = 0; + if (range > 1) + range = 1; + M_DrawCharacter (x-8, y, 128); + for (i = 0; i < SLIDER_RANGE; i++) + M_DrawCharacter (x + i*8, y, 129); + M_DrawCharacter (x+i*8, y, 130); + M_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131); + q_snprintf(buffer, 5, format, value); + M_Print (x + (SLIDER_RANGE + 1) * 8, y, buffer); +} + +void M_DrawCheckbox (int x, int y, int on) +{ +#if 0 + if (on) + M_DrawCharacter (x, y, 131); + else + M_DrawCharacter (x, y, 129); +#endif + if (on) + M_Print (x, y, "on"); + else + M_Print (x, y, "off"); +} + +void M_Options_Draw (void) +{ + float r, l; + qpic_t *p; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_option.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + // Draw the items in the order of the enum defined above: + // OPT_CUSTOMIZE: + M_Print (16, 32, " Controls"); + // OPT_CONSOLE: + M_Print (16, 32 + 8*OPT_CONSOLE, " Goto console"); + // OPT_DEFAULTS: + M_Print (16, 32 + 8*OPT_DEFAULTS, " Reset config"); + + // OPT_SCALE: + M_Print (16, 32 + 8*OPT_SCALE, " Scale"); + l = (vid.width / 320.0) - 1; + r = l > 0 ? (scr_conscale.value - 1) / l : 0; + M_DrawSlider (220, 32 + 8*OPT_SCALE, r, scr_conscale.value, "%.1f"); + + // OPT_SCRSIZE: + M_Print (16, 32 + 8*OPT_SCRSIZE, " Screen size"); + r = (scr_viewsize.value - 30) / (120 - 30); + M_DrawSlider (220, 32 + 8*OPT_SCRSIZE, r, scr_viewsize.value, "%.0f"); + + // OPT_GAMMA: + M_Print (16, 32 + 8*OPT_GAMMA, " Brightness"); + r = (1.0 - vid_gamma.value) / 0.5; + M_DrawSlider (220, 32 + 8*OPT_GAMMA, r, vid_gamma.value, "%.2f"); + + // OPT_CONTRAST: + M_Print (16, 32 + 8*OPT_CONTRAST, " Contrast"); + r = vid_contrast.value - 1.0; + M_DrawSlider (220, 32 + 8*OPT_CONTRAST, r, vid_contrast.value, "%.1f"); + + // OPT_MOUSESPEED: + M_Print (16, 32 + 8*OPT_MOUSESPEED, " Mouse Speed"); + r = (sensitivity.value - 1)/10; + M_DrawSlider (220, 32 + 8*OPT_MOUSESPEED, r, sensitivity.value, "%.1f"); + + // OPT_SBALPHA: + M_Print (16, 32 + 8*OPT_SBALPHA, " Statusbar alpha"); + r = (1.0 - scr_sbaralpha.value) ; // scr_sbaralpha range is 1.0 to 0.0 + M_DrawSlider (220, 32 + 8*OPT_SBALPHA, r, scr_sbaralpha.value, "%.2f"); + + // OPT_SNDVOL: + M_Print (16, 32 + 8*OPT_SNDVOL, " Sound Volume"); + r = sfxvolume.value; + M_DrawSlider (220, 32 + 8*OPT_SNDVOL, r, sfxvolume.value, "%.1f"); + + // OPT_MUSICVOL: + M_Print (16, 32 + 8*OPT_MUSICVOL, " Music Volume"); + r = bgmvolume.value; + M_DrawSlider (220, 32 + 8*OPT_MUSICVOL, r, bgmvolume.value, "%.1f"); + + // OPT_MUSICEXT: + M_Print (16, 32 + 8*OPT_MUSICEXT, " External Music"); + M_DrawCheckbox (220, 32 + 8*OPT_MUSICEXT, bgm_extmusic.value); + + // OPT_ALWAYRUN: + M_Print (16, 32 + 8*OPT_ALWAYRUN, " Always Run"); + if (cl_alwaysrun.value) + M_Print (220, 32 + 8*OPT_ALWAYRUN, "quakespasm"); + else if (cl_forwardspeed.value > 200.0) + M_Print (220, 32 + 8*OPT_ALWAYRUN, "vanilla"); + else + M_Print (220, 32 + 8*OPT_ALWAYRUN, "off"); + + // OPT_INVMOUSE: + M_Print (16, 32 + 8*OPT_INVMOUSE, " Invert Mouse"); + M_DrawCheckbox (220, 32 + 8*OPT_INVMOUSE, m_pitch.value < 0); + + // OPT_ALWAYSMLOOK: + M_Print (16, 32 + 8*OPT_ALWAYSMLOOK, " Mouse Look"); + M_DrawCheckbox (220, 32 + 8*OPT_ALWAYSMLOOK, in_mlook.state & 1); + + // OPT_LOOKSPRING: + M_Print (16, 32 + 8*OPT_LOOKSPRING, " Lookspring"); + M_DrawCheckbox (220, 32 + 8*OPT_LOOKSPRING, lookspring.value); + + // OPT_LOOKSTRAFE: + M_Print (16, 32 + 8*OPT_LOOKSTRAFE, " Lookstrafe"); + M_DrawCheckbox (220, 32 + 8*OPT_LOOKSTRAFE, lookstrafe.value); + + // OPT_VIDEO: + if (vid_menudrawfn) + M_Print (16, 32 + 8*OPT_VIDEO, " Video Options"); + +// cursor + M_DrawCharacter (200, 32 + options_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Options_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + m_entersound = true; + switch (options_cursor) + { + case OPT_CUSTOMIZE: + M_Menu_Keys_f (); + break; + case OPT_CONSOLE: + m_state = m_none; + Con_ToggleConsole_f (); + break; + case OPT_DEFAULTS: + if (SCR_ModalMessage("This will reset all controls\n" + "and stored cvars. Continue? (y/n)\n", 15.0f)) + { + Cbuf_AddText ("resetcfg\n"); + Cbuf_AddText ("exec default.cfg\n"); + } + break; + case OPT_VIDEO: + M_Menu_Video_f (); + break; + default: + M_AdjustSliders (1); + break; + } + return; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + options_cursor--; + if (options_cursor < 0) + options_cursor = OPTIONS_ITEMS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + options_cursor++; + if (options_cursor >= OPTIONS_ITEMS) + options_cursor = 0; + break; + + case K_LEFTARROW: + M_AdjustSliders (-1); + break; + + case K_RIGHTARROW: + M_AdjustSliders (1); + break; + } + + if (options_cursor == OPTIONS_ITEMS - 1 && vid_menudrawfn == NULL) + { + if (k == K_UPARROW) + options_cursor = OPTIONS_ITEMS - 2; + else + options_cursor = 0; + } +} + +//============================================================================= +/* KEYS MENU */ + +const char *bindnames[][2] = +{ + {"+attack", "attack"}, + {"impulse 10", "next weapon"}, + {"impulse 12", "prev weapon"}, + {"+jump", "jump / swim up"}, + {"+forward", "walk forward"}, + {"+back", "backpedal"}, + {"+left", "turn left"}, + {"+right", "turn right"}, + {"+speed", "run"}, + {"+moveleft", "step left"}, + {"+moveright", "step right"}, + {"+strafe", "sidestep"}, + {"+lookup", "look up"}, + {"+lookdown", "look down"}, + {"centerview", "center view"}, + {"+mlook", "mouse look"}, + {"+klook", "keyboard look"}, + {"+moveup", "swim up"}, + {"+movedown", "swim down"} +}; + +#define NUMCOMMANDS Q_COUNTOF(bindnames) + +static int keys_cursor; +static qboolean bind_grab; + +void M_Menu_Keys_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_keys; + m_entersound = true; +} + + +void M_FindKeysForCommand (const char *command, int *threekeys) +{ + int count; + int j; + int l; + char *b; + + threekeys[0] = threekeys[1] = threekeys[2] = -1; + l = strlen(command); + count = 0; + + for (j = 0; j < MAX_KEYS; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) ) + { + threekeys[count] = j; + count++; + if (count == 3) + break; + } + } +} + +void M_UnbindCommand (const char *command) +{ + int j; + int l; + char *b; + + l = strlen(command); + + for (j = 0; j < MAX_KEYS; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) ) + Key_SetBinding (j, NULL); + } +} + +extern qpic_t *pic_up, *pic_down; + +void M_Keys_Draw (void) +{ + int i, x, y; + int keys[3]; + const char *name; + qpic_t *p; + + p = Draw_CachePic ("gfx/ttl_cstm.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + if (bind_grab) + M_Print (12, 32, "Press a key or button for this action"); + else + M_Print (18, 32, "Enter to change, backspace to clear"); + +// search for known bindings + for (i = 0; i < (int)NUMCOMMANDS; i++) + { + y = 48 + 8*i; + + M_Print (16, y, bindnames[i][1]); + + M_FindKeysForCommand (bindnames[i][0], keys); + + if (keys[0] == -1) + { + M_Print (140, y, "???"); + } + else + { + name = Key_KeynumToString (keys[0]); + M_Print (140, y, name); + x = strlen(name) * 8; + if (keys[1] != -1) + { + name = Key_KeynumToString (keys[1]); + M_Print (140 + x + 8, y, "or"); + M_Print (140 + x + 32, y, name); + x = x + 32 + strlen(name) * 8; + if (keys[2] != -1) + { + M_Print (140 + x + 8, y, "or"); + M_Print (140 + x + 32, y, Key_KeynumToString (keys[2])); + } + } + } + } + + if (bind_grab) + M_DrawCharacter (130, 48 + keys_cursor*8, '='); + else + M_DrawCharacter (130, 48 + keys_cursor*8, 12+((int)(realtime*4)&1)); +} + + +void M_Keys_Key (int k) +{ + char cmd[80]; + int keys[3]; + + if (bind_grab) + { // defining a key + S_LocalSound ("misc/menu1.wav"); + if ((k != K_ESCAPE) && (k != '`')) + { + sprintf (cmd, "bind \"%s\" \"%s\"\n", Key_KeynumToString (k), bindnames[keys_cursor][0]); + Cbuf_InsertText (cmd); + } + + bind_grab = false; + IN_Deactivate(modestate == MS_WINDOWED); // deactivate because we're returning to the menu + return; + } + + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Options_f (); + break; + + case K_LEFTARROW: + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + keys_cursor--; + if (keys_cursor < 0) + keys_cursor = NUMCOMMANDS-1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + keys_cursor++; + if (keys_cursor >= (int)NUMCOMMANDS) + keys_cursor = 0; + break; + + case K_ENTER: // go into bind mode + case K_KP_ENTER: + case K_ABUTTON: + M_FindKeysForCommand (bindnames[keys_cursor][0], keys); + S_LocalSound ("misc/menu2.wav"); + if (keys[2] != -1) + M_UnbindCommand (bindnames[keys_cursor][0]); + bind_grab = true; + IN_Activate(); // activate to allow mouse key binding + break; + + case K_BACKSPACE: // delete bindings + case K_DEL: + S_LocalSound ("misc/menu2.wav"); + M_UnbindCommand (bindnames[keys_cursor][0]); + break; + } +} + +//============================================================================= +/* VIDEO MENU */ + +void M_Menu_Video_f (void) +{ + (*vid_menucmdfn) (); //johnfitz +} + + +void M_Video_Draw (void) +{ + (*vid_menudrawfn) (); +} + + +void M_Video_Key (int key) +{ + (*vid_menukeyfn) (key); +} + +//============================================================================= +/* HELP MENU */ + +int help_page; +#define NUM_HELP_PAGES 6 + + +void M_Menu_Help_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_help; + m_entersound = true; + help_page = 0; +} + + + +void M_Help_Draw (void) +{ + M_DrawPic (0, 0, Draw_CachePic ( va("gfx/help%i.lmp", help_page)) ); +} + + +void M_Help_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Main_f (); + break; + + case K_UPARROW: + case K_RIGHTARROW: + m_entersound = true; + if (++help_page >= NUM_HELP_PAGES) + help_page = 0; + break; + + case K_DOWNARROW: + case K_LEFTARROW: + m_entersound = true; + if (--help_page < 0) + help_page = NUM_HELP_PAGES-1; + break; + } + +} + +//============================================================================= +/* QUIT MENU */ + +int msgNumber; +enum m_state_e m_quit_prevstate; +qboolean wasInMenus; + +void M_Menu_Quit_f (void) +{ + if (m_state == m_quit) + return; + wasInMenus = (key_dest == key_menu); + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_quit_prevstate = m_state; + m_state = m_quit; + m_entersound = true; + msgNumber = rand()&7; +} + + +void M_Quit_Key (int key) +{ + if (key == K_ESCAPE) + { + if (wasInMenus) + { + m_state = m_quit_prevstate; + m_entersound = true; + } + else + { + IN_Activate(); + key_dest = key_game; + m_state = m_none; + } + } +} + + +void M_Quit_Char (int key) +{ + switch (key) + { + case 'n': + case 'N': + if (wasInMenus) + { + m_state = m_quit_prevstate; + m_entersound = true; + } + else + { + IN_Activate(); + key_dest = key_game; + m_state = m_none; + } + break; + + case 'y': + case 'Y': + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_console; + Host_Quit_f (); + break; + + default: + break; + } + +} + + +qboolean M_Quit_TextEntry (void) +{ + return true; +} + + +void M_Quit_Draw (void) //johnfitz -- modified for new quit message +{ + char msg1[] = "QuakeSpasm " QUAKESPASM_VER_STRING; + char msg2[] = "by Ozkan Sezer,Eric Wasylishen,others"; /* msg2/msg3 are [38] at most */ + char msg3[] = "Press y to quit"; + int boxlen; + + if (wasInMenus) + { + m_state = m_quit_prevstate; + m_recursiveDraw = true; + M_Draw (); + m_state = m_quit; + } + + //okay, this is kind of fucked up. M_DrawTextBox will always act as if + //width is even. Also, the width and lines values are for the interior of the box, + //but the x and y values include the border. + boxlen = (q_max(sizeof(msg1), q_max(sizeof(msg2),sizeof(msg3))) + 1) & ~1; + M_DrawTextBox (160-4*(boxlen+2), 76, boxlen, 4); + + //now do the text + M_Print (160-4*(sizeof(msg1)-1), 88, msg1); + M_Print (160-4*(sizeof(msg2)-1), 96, msg2); + M_PrintWhite (160-4*(sizeof(msg3)-1), 104, msg3); +} + +//============================================================================= +/* LAN CONFIG MENU */ + +int lanConfig_cursor = -1; +int lanConfig_cursor_table [] = {72, 92, 124}; +#define NUM_LANCONFIG_CMDS 3 + +int lanConfig_port; +char lanConfig_portname[6]; +char lanConfig_joinname[22]; + +void M_Menu_LanConfig_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_lanconfig; + m_entersound = true; + if (lanConfig_cursor == -1) + { + if (JoiningGame && TCPIPConfig) + lanConfig_cursor = 2; + else + lanConfig_cursor = 1; + } + if (StartingGame && lanConfig_cursor == 2) + lanConfig_cursor = 1; + lanConfig_port = DEFAULTnet_hostport; + sprintf(lanConfig_portname, "%u", lanConfig_port); + + m_return_onerror = false; + m_return_reason[0] = 0; +} + + +void M_LanConfig_Draw (void) +{ + qpic_t *p; + int basex; + const char *startJoin; + const char *protocol; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + basex = (320-p->width)/2; + M_DrawPic (basex, 4, p); + + basex = 72; /* Arcane Dimensions has an oversized gfx/p_multi.lmp */ + + if (StartingGame) + startJoin = "New Game"; + else + startJoin = "Join Game"; + if (IPXConfig) + protocol = "IPX"; + else + protocol = "TCP/IP"; + M_Print (basex, 32, va ("%s - %s", startJoin, protocol)); + basex += 8; + + M_Print (basex, 52, "Address:"); + if (IPXConfig) + M_Print (basex+9*8, 52, my_ipx_address); + else + M_Print (basex+9*8, 52, my_tcpip_address); + + M_Print (basex, lanConfig_cursor_table[0], "Port"); + M_DrawTextBox (basex+8*8, lanConfig_cursor_table[0]-8, 6, 1); + M_Print (basex+9*8, lanConfig_cursor_table[0], lanConfig_portname); + + if (JoiningGame) + { + M_Print (basex, lanConfig_cursor_table[1], "Search for local games..."); + M_Print (basex, 108, "Join game at:"); + M_DrawTextBox (basex+8, lanConfig_cursor_table[2]-8, 22, 1); + M_Print (basex+16, lanConfig_cursor_table[2], lanConfig_joinname); + } + else + { + M_DrawTextBox (basex, lanConfig_cursor_table[1]-8, 2, 1); + M_Print (basex+8, lanConfig_cursor_table[1], "OK"); + } + + M_DrawCharacter (basex-8, lanConfig_cursor_table [lanConfig_cursor], 12+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 0) + M_DrawCharacter (basex+9*8 + 8*strlen(lanConfig_portname), lanConfig_cursor_table [0], 10+((int)(realtime*4)&1)); + + if (lanConfig_cursor == 2) + M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [2], 10+((int)(realtime*4)&1)); + + if (*m_return_reason) + M_PrintWhite (basex, 148, m_return_reason); +} + + +void M_LanConfig_Key (int key) +{ + int l; + + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Net_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + lanConfig_cursor--; + if (lanConfig_cursor < 0) + lanConfig_cursor = NUM_LANCONFIG_CMDS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + lanConfig_cursor++; + if (lanConfig_cursor >= NUM_LANCONFIG_CMDS) + lanConfig_cursor = 0; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + if (lanConfig_cursor == 0) + break; + + m_entersound = true; + + M_ConfigureNetSubsystem (); + + if (lanConfig_cursor == 1) + { + if (StartingGame) + { + M_Menu_GameOptions_f (); + break; + } + M_Menu_Search_f(); + break; + } + + if (lanConfig_cursor == 2) + { + m_return_state = m_state; + m_return_onerror = true; + IN_Activate(); + key_dest = key_game; + m_state = m_none; + Cbuf_AddText ( va ("connect \"%s\"\n", lanConfig_joinname) ); + break; + } + + break; + + case K_BACKSPACE: + if (lanConfig_cursor == 0) + { + if (strlen(lanConfig_portname)) + lanConfig_portname[strlen(lanConfig_portname)-1] = 0; + } + + if (lanConfig_cursor == 2) + { + if (strlen(lanConfig_joinname)) + lanConfig_joinname[strlen(lanConfig_joinname)-1] = 0; + } + break; + } + + if (StartingGame && lanConfig_cursor == 2) + { + if (key == K_UPARROW) + lanConfig_cursor = 1; + else + lanConfig_cursor = 0; + } + + l = Q_atoi(lanConfig_portname); + if (l > 65535) + l = lanConfig_port; + else + lanConfig_port = l; + sprintf(lanConfig_portname, "%u", lanConfig_port); +} + + +void M_LanConfig_Char (int key) +{ + int l; + + switch (lanConfig_cursor) + { + case 0: + if (key < '0' || key > '9') + return; + l = strlen(lanConfig_portname); + if (l < 5) + { + lanConfig_portname[l+1] = 0; + lanConfig_portname[l] = key; + } + break; + case 2: + l = strlen(lanConfig_joinname); + if (l < 21) + { + lanConfig_joinname[l+1] = 0; + lanConfig_joinname[l] = key; + } + break; + } +} + + +qboolean M_LanConfig_TextEntry (void) +{ + return (lanConfig_cursor == 0 || lanConfig_cursor == 2); +} + +//============================================================================= +/* GAME OPTIONS MENU */ + +typedef struct +{ + const char *name; + const char *description; +} level_t; + +level_t levels[] = +{ + {"start", "Entrance"}, // 0 + + {"e1m1", "Slipgate Complex"}, // 1 + {"e1m2", "Castle of the Damned"}, + {"e1m3", "The Necropolis"}, + {"e1m4", "The Grisly Grotto"}, + {"e1m5", "Gloom Keep"}, + {"e1m6", "The Door To Chthon"}, + {"e1m7", "The House of Chthon"}, + {"e1m8", "Ziggurat Vertigo"}, + + {"e2m1", "The Installation"}, // 9 + {"e2m2", "Ogre Citadel"}, + {"e2m3", "Crypt of Decay"}, + {"e2m4", "The Ebon Fortress"}, + {"e2m5", "The Wizard's Manse"}, + {"e2m6", "The Dismal Oubliette"}, + {"e2m7", "Underearth"}, + + {"e3m1", "Termination Central"}, // 16 + {"e3m2", "The Vaults of Zin"}, + {"e3m3", "The Tomb of Terror"}, + {"e3m4", "Satan's Dark Delight"}, + {"e3m5", "Wind Tunnels"}, + {"e3m6", "Chambers of Torment"}, + {"e3m7", "The Haunted Halls"}, + + {"e4m1", "The Sewage System"}, // 23 + {"e4m2", "The Tower of Despair"}, + {"e4m3", "The Elder God Shrine"}, + {"e4m4", "The Palace of Hate"}, + {"e4m5", "Hell's Atrium"}, + {"e4m6", "The Pain Maze"}, + {"e4m7", "Azure Agony"}, + {"e4m8", "The Nameless City"}, + + {"end", "Shub-Niggurath's Pit"}, // 31 + + {"dm1", "Place of Two Deaths"}, // 32 + {"dm2", "Claustrophobopolis"}, + {"dm3", "The Abandoned Base"}, + {"dm4", "The Bad Place"}, + {"dm5", "The Cistern"}, + {"dm6", "The Dark Zone"} +}; + +//MED 01/06/97 added hipnotic levels +level_t hipnoticlevels[] = +{ + {"start", "Command HQ"}, // 0 + + {"hip1m1", "The Pumping Station"}, // 1 + {"hip1m2", "Storage Facility"}, + {"hip1m3", "The Lost Mine"}, + {"hip1m4", "Research Facility"}, + {"hip1m5", "Military Complex"}, + + {"hip2m1", "Ancient Realms"}, // 6 + {"hip2m2", "The Black Cathedral"}, + {"hip2m3", "The Catacombs"}, + {"hip2m4", "The Crypt"}, + {"hip2m5", "Mortum's Keep"}, + {"hip2m6", "The Gremlin's Domain"}, + + {"hip3m1", "Tur Torment"}, // 12 + {"hip3m2", "Pandemonium"}, + {"hip3m3", "Limbo"}, + {"hip3m4", "The Gauntlet"}, + + {"hipend", "Armagon's Lair"}, // 16 + + {"hipdm1", "The Edge of Oblivion"} // 17 +}; + +//PGM 01/07/97 added rogue levels +//PGM 03/02/97 added dmatch level +level_t roguelevels[] = +{ + {"start", "Split Decision"}, + {"r1m1", "Deviant's Domain"}, + {"r1m2", "Dread Portal"}, + {"r1m3", "Judgement Call"}, + {"r1m4", "Cave of Death"}, + {"r1m5", "Towers of Wrath"}, + {"r1m6", "Temple of Pain"}, + {"r1m7", "Tomb of the Overlord"}, + {"r2m1", "Tempus Fugit"}, + {"r2m2", "Elemental Fury I"}, + {"r2m3", "Elemental Fury II"}, + {"r2m4", "Curse of Osiris"}, + {"r2m5", "Wizard's Keep"}, + {"r2m6", "Blood Sacrifice"}, + {"r2m7", "Last Bastion"}, + {"r2m8", "Source of Evil"}, + {"ctf1", "Division of Change"} +}; + +typedef struct +{ + const char *description; + int firstLevel; + int levels; +} episode_t; + +episode_t episodes[] = +{ + {"Welcome to Quake", 0, 1}, + {"Doomed Dimension", 1, 8}, + {"Realm of Black Magic", 9, 7}, + {"Netherworld", 16, 7}, + {"The Elder World", 23, 8}, + {"Final Level", 31, 1}, + {"Deathmatch Arena", 32, 6} +}; + +//MED 01/06/97 added hipnotic episodes +episode_t hipnoticepisodes[] = +{ + {"Scourge of Armagon", 0, 1}, + {"Fortress of the Dead", 1, 5}, + {"Dominion of Darkness", 6, 6}, + {"The Rift", 12, 4}, + {"Final Level", 16, 1}, + {"Deathmatch Arena", 17, 1} +}; + +//PGM 01/07/97 added rogue episodes +//PGM 03/02/97 added dmatch episode +episode_t rogueepisodes[] = +{ + {"Introduction", 0, 1}, + {"Hell's Fortress", 1, 7}, + {"Corridors of Time", 8, 8}, + {"Deathmatch Arena", 16, 1} +}; + +int startepisode; +int startlevel; +int maxplayers; +qboolean m_serverInfoMessage = false; +double m_serverInfoMessageTime; + +void M_Menu_GameOptions_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_gameoptions; + m_entersound = true; + if (maxplayers == 0) + maxplayers = svs.maxclients; + if (maxplayers < 2) + maxplayers = svs.maxclientslimit; +} + + +int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 112, 120}; +#define NUM_GAMEOPTIONS 9 +int gameoptions_cursor; + +void M_GameOptions_Draw (void) +{ + qpic_t *p; + int x; + + M_DrawTransPic (16, 4, Draw_CachePic ("gfx/qplaque.lmp") ); + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + + M_DrawTextBox (152, 32, 10, 1); + M_Print (160, 40, "begin game"); + + M_Print (0, 56, " Max players"); + M_Print (160, 56, va("%i", maxplayers) ); + + M_Print (0, 64, " Game Type"); + if (coop.value) + M_Print (160, 64, "Cooperative"); + else + M_Print (160, 64, "Deathmatch"); + + M_Print (0, 72, " Teamplay"); + if (rogue) + { + const char *msg; + + switch((int)teamplay.value) + { + case 1: msg = "No Friendly Fire"; break; + case 2: msg = "Friendly Fire"; break; + case 3: msg = "Tag"; break; + case 4: msg = "Capture the Flag"; break; + case 5: msg = "One Flag CTF"; break; + case 6: msg = "Three Team CTF"; break; + default: msg = "Off"; break; + } + M_Print (160, 72, msg); + } + else + { + const char *msg; + + switch((int)teamplay.value) + { + case 1: msg = "No Friendly Fire"; break; + case 2: msg = "Friendly Fire"; break; + default: msg = "Off"; break; + } + M_Print (160, 72, msg); + } + + M_Print (0, 80, " Skill"); + if (skill.value == 0) + M_Print (160, 80, "Easy difficulty"); + else if (skill.value == 1) + M_Print (160, 80, "Normal difficulty"); + else if (skill.value == 2) + M_Print (160, 80, "Hard difficulty"); + else + M_Print (160, 80, "Nightmare difficulty"); + + M_Print (0, 88, " Frag Limit"); + if (fraglimit.value == 0) + M_Print (160, 88, "none"); + else + M_Print (160, 88, va("%i frags", (int)fraglimit.value)); + + M_Print (0, 96, " Time Limit"); + if (timelimit.value == 0) + M_Print (160, 96, "none"); + else + M_Print (160, 96, va("%i minutes", (int)timelimit.value)); + + M_Print (0, 112, " Episode"); + // MED 01/06/97 added hipnotic episodes + if (hipnotic) + M_Print (160, 112, hipnoticepisodes[startepisode].description); + // PGM 01/07/97 added rogue episodes + else if (rogue) + M_Print (160, 112, rogueepisodes[startepisode].description); + else + M_Print (160, 112, episodes[startepisode].description); + + M_Print (0, 120, " Level"); + // MED 01/06/97 added hipnotic episodes + if (hipnotic) + { + M_Print (160, 120, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].description); + M_Print (160, 128, hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name); + } + // PGM 01/07/97 added rogue episodes + else if (rogue) + { + M_Print (160, 120, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].description); + M_Print (160, 128, roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name); + } + else + { + M_Print (160, 120, levels[episodes[startepisode].firstLevel + startlevel].description); + M_Print (160, 128, levels[episodes[startepisode].firstLevel + startlevel].name); + } + +// line cursor + M_DrawCharacter (144, gameoptions_cursor_table[gameoptions_cursor], 12+((int)(realtime*4)&1)); + + if (m_serverInfoMessage) + { + if ((realtime - m_serverInfoMessageTime) < 5.0) + { + x = (320-26*8)/2; + M_DrawTextBox (x, 138, 24, 4); + x += 8; + M_Print (x, 146, " More than 4 players "); + M_Print (x, 154, " requires using command "); + M_Print (x, 162, "line parameters; please "); + M_Print (x, 170, " see techinfo.txt. "); + } + else + { + m_serverInfoMessage = false; + } + } +} + + +void M_NetStart_Change (int dir) +{ + int count; + float f; + + switch (gameoptions_cursor) + { + case 1: + maxplayers += dir; + if (maxplayers > svs.maxclientslimit) + { + maxplayers = svs.maxclientslimit; + m_serverInfoMessage = true; + m_serverInfoMessageTime = realtime; + } + if (maxplayers < 2) + maxplayers = 2; + break; + + case 2: + Cvar_Set ("coop", coop.value ? "0" : "1"); + break; + + case 3: + count = (rogue) ? 6 : 2; + f = teamplay.value + dir; + if (f > count) f = 0; + else if (f < 0) f = count; + Cvar_SetValue ("teamplay", f); + break; + + case 4: + f = skill.value + dir; + if (f > 3) f = 0; + else if (f < 0) f = 3; + Cvar_SetValue ("skill", f); + break; + + case 5: + f = fraglimit.value + dir * 10; + if (f > 100) f = 0; + else if (f < 0) f = 100; + Cvar_SetValue ("fraglimit", f); + break; + + case 6: + f = timelimit.value + dir * 5; + if (f > 60) f = 0; + else if (f < 0) f = 60; + Cvar_SetValue ("timelimit", f); + break; + + case 7: + startepisode += dir; + //MED 01/06/97 added hipnotic count + if (hipnotic) + count = 6; + //PGM 01/07/97 added rogue count + //PGM 03/02/97 added 1 for dmatch episode + else if (rogue) + count = 4; + else if (registered.value) + count = 7; + else + count = 2; + + if (startepisode < 0) + startepisode = count - 1; + + if (startepisode >= count) + startepisode = 0; + + startlevel = 0; + break; + + case 8: + startlevel += dir; + //MED 01/06/97 added hipnotic episodes + if (hipnotic) + count = hipnoticepisodes[startepisode].levels; + //PGM 01/06/97 added hipnotic episodes + else if (rogue) + count = rogueepisodes[startepisode].levels; + else + count = episodes[startepisode].levels; + + if (startlevel < 0) + startlevel = count - 1; + + if (startlevel >= count) + startlevel = 0; + break; + } +} + +void M_GameOptions_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_Net_f (); + break; + + case K_UPARROW: + S_LocalSound ("misc/menu1.wav"); + gameoptions_cursor--; + if (gameoptions_cursor < 0) + gameoptions_cursor = NUM_GAMEOPTIONS-1; + break; + + case K_DOWNARROW: + S_LocalSound ("misc/menu1.wav"); + gameoptions_cursor++; + if (gameoptions_cursor >= NUM_GAMEOPTIONS) + gameoptions_cursor = 0; + break; + + case K_LEFTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("misc/menu3.wav"); + M_NetStart_Change (-1); + break; + + case K_RIGHTARROW: + if (gameoptions_cursor == 0) + break; + S_LocalSound ("misc/menu3.wav"); + M_NetStart_Change (1); + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + S_LocalSound ("misc/menu2.wav"); + if (gameoptions_cursor == 0) + { + if (sv.active) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText ("listen 0\n"); // so host_netport will be re-examined + Cbuf_AddText ( va ("maxplayers %u\n", maxplayers) ); + SCR_BeginLoadingPlaque (); + + if (hipnotic) + Cbuf_AddText ( va ("map %s\n", hipnoticlevels[hipnoticepisodes[startepisode].firstLevel + startlevel].name) ); + else if (rogue) + Cbuf_AddText ( va ("map %s\n", roguelevels[rogueepisodes[startepisode].firstLevel + startlevel].name) ); + else + Cbuf_AddText ( va ("map %s\n", levels[episodes[startepisode].firstLevel + startlevel].name) ); + + return; + } + + M_NetStart_Change (1); + break; + } +} + +//============================================================================= +/* SEARCH MENU */ + +qboolean searchComplete = false; +double searchCompleteTime; + +void M_Menu_Search_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_search; + m_entersound = false; + slistSilent = true; + slistLocal = false; + searchComplete = false; + NET_Slist_f(); + +} + + +void M_Search_Draw (void) +{ + qpic_t *p; + int x; + + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + x = (320/2) - ((12*8)/2) + 4; + M_DrawTextBox (x-8, 32, 12, 1); + M_Print (x, 40, "Searching..."); + + if(slistInProgress) + { + NET_Poll(); + return; + } + + if (! searchComplete) + { + searchComplete = true; + searchCompleteTime = realtime; + } + + if (hostCacheCount) + { + M_Menu_ServerList_f (); + return; + } + + M_PrintWhite ((320/2) - ((22*8)/2), 64, "No Quake servers found"); + if ((realtime - searchCompleteTime) < 3.0) + return; + + M_Menu_LanConfig_f (); +} + + +void M_Search_Key (int key) +{ +} + +//============================================================================= +/* SLIST MENU */ + +int slist_cursor; +qboolean slist_sorted; + +void M_Menu_ServerList_f (void) +{ + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_slist; + m_entersound = true; + slist_cursor = 0; + m_return_onerror = false; + m_return_reason[0] = 0; + slist_sorted = false; +} + + +void M_ServerList_Draw (void) +{ + int n; + qpic_t *p; + + if (!slist_sorted) + { + slist_sorted = true; + NET_SlistSort (); + } + + p = Draw_CachePic ("gfx/p_multi.lmp"); + M_DrawPic ( (320-p->width)/2, 4, p); + for (n = 0; n < hostCacheCount; n++) + M_Print (16, 32 + 8*n, NET_SlistPrintServer (n)); + M_DrawCharacter (0, 32 + slist_cursor*8, 12+((int)(realtime*4)&1)); + + if (*m_return_reason) + M_PrintWhite (16, 148, m_return_reason); +} + + +void M_ServerList_Key (int k) +{ + switch (k) + { + case K_ESCAPE: + case K_BBUTTON: + M_Menu_LanConfig_f (); + break; + + case K_SPACE: + M_Menu_Search_f (); + break; + + case K_UPARROW: + case K_LEFTARROW: + S_LocalSound ("misc/menu1.wav"); + slist_cursor--; + if (slist_cursor < 0) + slist_cursor = hostCacheCount - 1; + break; + + case K_DOWNARROW: + case K_RIGHTARROW: + S_LocalSound ("misc/menu1.wav"); + slist_cursor++; + if (slist_cursor >= hostCacheCount) + slist_cursor = 0; + break; + + case K_ENTER: + case K_KP_ENTER: + case K_ABUTTON: + S_LocalSound ("misc/menu2.wav"); + m_return_state = m_state; + m_return_onerror = true; + slist_sorted = false; + IN_Activate(); + key_dest = key_game; + m_state = m_none; + Cbuf_AddText ( va ("connect \"%s\"\n", NET_SlistPrintServerName(slist_cursor)) ); + break; + + default: + break; + } + +} + +//============================================================================= +/* Credits menu -- used by the 2021 re-release */ + +void M_Menu_Credits_f (void) +{ +} + +//============================================================================= +/* Menu Subsystem */ + +void M_Init (void) +{ + Cmd_AddCommand ("togglemenu", M_ToggleMenu_f); + + Cmd_AddCommand ("menu_main", M_Menu_Main_f); + Cmd_AddCommand ("menu_singleplayer", M_Menu_SinglePlayer_f); + Cmd_AddCommand ("menu_load", M_Menu_Load_f); + Cmd_AddCommand ("menu_save", M_Menu_Save_f); + Cmd_AddCommand ("menu_multiplayer", M_Menu_MultiPlayer_f); + Cmd_AddCommand ("menu_setup", M_Menu_Setup_f); + Cmd_AddCommand ("menu_options", M_Menu_Options_f); + Cmd_AddCommand ("menu_keys", M_Menu_Keys_f); + Cmd_AddCommand ("menu_video", M_Menu_Video_f); + Cmd_AddCommand ("help", M_Menu_Help_f); + Cmd_AddCommand ("menu_quit", M_Menu_Quit_f); + Cmd_AddCommand ("menu_credits", M_Menu_Credits_f); // needed by the 2021 re-release +} + + +void M_Draw (void) +{ + if (m_state == m_none || key_dest != key_menu) + return; + + if (!m_recursiveDraw) + { + if (scr_con_current) + { + Draw_ConsoleBackground (); + S_ExtraUpdate (); + } + + Draw_FadeScreen (); //johnfitz -- fade even if console fills screen + } + else + { + m_recursiveDraw = false; + } + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + switch (m_state) + { + case m_none: + break; + + case m_main: + M_Main_Draw (); + break; + + case m_singleplayer: + M_SinglePlayer_Draw (); + break; + + case m_load: + M_Load_Draw (); + break; + + case m_save: + M_Save_Draw (); + break; + + case m_multiplayer: + M_MultiPlayer_Draw (); + break; + + case m_setup: + M_Setup_Draw (); + break; + + case m_net: + M_Net_Draw (); + break; + + case m_options: + M_Options_Draw (); + break; + + case m_keys: + M_Keys_Draw (); + break; + + case m_video: + M_Video_Draw (); + break; + + case m_help: + M_Help_Draw (); + break; + + case m_quit: + if (!fitzmode) + { /* QuakeSpasm customization: */ + /* Quit now! S.A. */ + key_dest = key_console; + Host_Quit_f (); + } + M_Quit_Draw (); + break; + + case m_lanconfig: + M_LanConfig_Draw (); + break; + + case m_gameoptions: + M_GameOptions_Draw (); + break; + + case m_search: + M_Search_Draw (); + break; + + case m_slist: + M_ServerList_Draw (); + break; + } + + if (m_entersound) + { + S_LocalSound ("misc/menu2.wav"); + m_entersound = false; + } + + S_ExtraUpdate (); +} + + +void M_Keydown (int key) +{ + switch (m_state) + { + case m_none: + return; + + case m_main: + M_Main_Key (key); + return; + + case m_singleplayer: + M_SinglePlayer_Key (key); + return; + + case m_load: + M_Load_Key (key); + return; + + case m_save: + M_Save_Key (key); + return; + + case m_multiplayer: + M_MultiPlayer_Key (key); + return; + + case m_setup: + M_Setup_Key (key); + return; + + case m_net: + M_Net_Key (key); + return; + + case m_options: + M_Options_Key (key); + return; + + case m_keys: + M_Keys_Key (key); + return; + + case m_video: + M_Video_Key (key); + return; + + case m_help: + M_Help_Key (key); + return; + + case m_quit: + M_Quit_Key (key); + return; + + case m_lanconfig: + M_LanConfig_Key (key); + return; + + case m_gameoptions: + M_GameOptions_Key (key); + return; + + case m_search: + M_Search_Key (key); + break; + + case m_slist: + M_ServerList_Key (key); + return; + } +} + + +void M_Charinput (int key) +{ + switch (m_state) + { + case m_setup: + M_Setup_Char (key); + return; + case m_quit: + M_Quit_Char (key); + return; + case m_lanconfig: + M_LanConfig_Char (key); + return; + default: + return; + } +} + + +qboolean M_TextEntry (void) +{ + switch (m_state) + { + case m_setup: + return M_Setup_TextEntry (); + case m_quit: + return M_Quit_TextEntry (); + case m_lanconfig: + return M_LanConfig_TextEntry (); + default: + return false; + } +} + + +void M_ConfigureNetSubsystem(void) +{ +// enable/disable net systems to match desired config + Cbuf_AddText ("stopdemo\n"); + + if (IPXConfig || TCPIPConfig) + net_hostport = lanConfig_port; +} + diff --git a/Quake/menu.hpp b/Quake/menu.hpp new file mode 100644 index 0000000..6e2c9ef --- /dev/null +++ b/Quake/menu.hpp @@ -0,0 +1,75 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_MENU_H +#define _QUAKE_MENU_H + +enum m_state_e { + m_none, + m_main, + m_singleplayer, + m_load, + m_save, + m_multiplayer, + m_setup, + m_net, + m_options, + m_video, + m_keys, + m_help, + m_quit, + m_lanconfig, + m_gameoptions, + m_search, + m_slist +}; + +extern enum m_state_e m_state; +extern enum m_state_e m_return_state; + +extern qboolean m_entersound; + +// +// menus +// +void M_Init (void); +void M_Keydown (int key); +void M_Charinput (int key); +qboolean M_TextEntry (void); +void M_ToggleMenu_f (void); + +void M_Menu_Main_f (void); +void M_Menu_Options_f (void); +void M_Menu_Quit_f (void); + +void M_Print (int cx, int cy, const char *str); +void M_PrintWhite (int cx, int cy, const char *str); + +void M_Draw (void); +void M_DrawCharacter (int cx, int line, int num); + +void M_DrawPic (int x, int y, qpic_t *pic); +void M_DrawTransPic (int x, int y, qpic_t *pic); +void M_DrawCheckbox (int x, int y, int on); + +#endif /* _QUAKE_MENU_H */ + diff --git a/Quake/miniz.cpp b/Quake/miniz.cpp new file mode 100644 index 0000000..c76af2a --- /dev/null +++ b/Quake/miniz.cpp @@ -0,0 +1,2282 @@ +#include "miniz.hpp" + +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * 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. + * + **************************************************************************/ + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS +/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ +#if 0 + static mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#elif defined(USE_EXTERNAL_MZCRC) +/* If USE_EXTERNAL_CRC is defined, an external module will export the + * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. + * Depending on the impl, it may be necessary to ~ the input/output crc values. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); +#else +/* Faster, but larger CPU cache footprint. + */ +static mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif +#endif /* MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS */ + +static /*MINIZ_EXPORT*/ void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +static /*MINIZ_EXPORT*/ void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +static /*MINIZ_EXPORT*/ void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to +*/ + +#ifndef MINIZ_NO_INFLATE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Decompression (completely independent from all compression API's) */ + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) \ + { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do \ + { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do \ + { \ + for (;;) \ + { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +#define TINFL_GET_BYTE(state_index, c) \ + do \ + { \ + while (pIn_buf_cur >= pIn_buf_end) \ + { \ + TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ + } \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do \ + { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ +/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree) \ + do \ + { \ + temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + code_len_hack = code_len; /* FIXME: workaround for miniz/#229 */ \ + } \ + MZ_MACRO_END + +static void tinfl_clear_tree(tinfl_decompressor *r) +{ + if (r->m_type == 0) + MZ_CLEAR_ARR(r->m_tree_0); + else if (r->m_type == 1) + MZ_CLEAR_ARR(r->m_tree_1); + else + MZ_CLEAR_ARR(r->m_tree_2); +} + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const mz_uint16 s_length_base[31] = { 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, 0, 0 }; + static const mz_uint8 s_length_extra[31] = { 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, 0, 0 }; + static const mz_uint16 s_dist_base[32] = { 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, 0, 0 }; + static const mz_uint8 s_dist_extra[32] = { 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 }; + static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 }; + + mz_int16 *pTrees[3]; + mz_uint8 *pCode_sizes[3]; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + pTrees[0] = r->m_tree_0; + pTrees[1] = r->m_tree_1; + pTrees[2] = r->m_tree_2; + pCode_sizes[0] = r->m_code_size_0; + pCode_sizes[1] = r->m_code_size_1; + pCode_sizes[2] = r->m_code_size_2; + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + mz_uint code_len_hack; /* FIXME: workaround for miniz/#229 */ + if (r->m_type == 1) + { + mz_uint8 *p = r->m_code_size_0; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_code_size_1, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_ARR(r->m_code_size_2); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + mz_int16 *pLookUp; + mz_int16 *pTree; + mz_uint8 *pCode_size; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pLookUp = r->m_look_up[r->m_type]; + pTree = pTrees[r->m_type]; + pCode_size = pCode_sizes[r->m_type]; + MZ_CLEAR_ARR(total_syms); + TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0])); + tinfl_clear_tree(r); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pCode_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pLookUp[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTree[-tree_cur - 1]) + { + pTree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0); + /* FIXME: workaround for miniz/#229 */ + if (!code_len_hack) + TINFL_CR_RETURN_FOREVER(101, TINFL_STATUS_FAILED); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + /* FIXME: workaround for miniz/#229 */ + if (!code_len) + TINFL_CR_RETURN_FOREVER(100, TINFL_STATUS_FAILED); + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); +#else + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; +#endif + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + while(counter>2) + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + counter -= 3; + } + if (counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + + /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ + TINFL_SKIP_BITS(32, num_bits & 7); + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits); + MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ + + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + for (counter = 0; counter < 4; ++counter) + { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + + TINFL_CR_FINISH + +common_exit: + /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ + /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ + if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) + { + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + } + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * 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. + * + **************************************************************************/ + + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + mz_uint32 m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + void *m_pMem; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > (mz_uint64)MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > (mz_uint64)MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > (mz_uint64)MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data; + void* buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) + { + buf = MZ_MALLOC(ext_data_size); + if(buf==NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8*)buf; + } + else + { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const mz_uint32 size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + mz_uint32 file_index = pIndices[(mz_uint32)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +static +mz_bool mz_zip_reader_extract_to_mem_no_alloc1(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size, const mz_zip_archive_file_stat *st) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (st) { + file_stat = *st; + } else + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +#if 0 /* unused for now */ +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL); +} +#endif + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_uint64 alloc_size; + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return NULL; + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +/* ------------------- Misc utils */ + +#if 0 /* unused for now */ +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write calledback failed"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); + + return MZ_FALSE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} +#endif /* unused */ + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/Quake/miniz.hpp b/Quake/miniz.hpp new file mode 100644 index 0000000..362a8ca --- /dev/null +++ b/Quake/miniz.hpp @@ -0,0 +1,726 @@ +#ifndef Q_MINIZ_H +#define Q_MINIZ_H 1 + +#ifndef MINIZ_EXPORT +#define MINIZ_EXPORT +#endif + +/* miniz.c 2.2.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateReset/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#if 1 /* QUAKESPASM-SPECIFIC CONFIG: */ + +#ifndef NDEBUG +#define NDEBUG /* disable assert()s */ +#endif + +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#ifdef USE_SDL2 +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */ + +/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ +#define MINIZ_NO_STDIO + +/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ +/* get/set file times, and the C run-time funcs that get/set times won't be called. */ +/* The current downside is the times written to your archives will be from 1979. */ +#define MINIZ_NO_TIME + +/* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */ +#define MINIZ_NO_DEFLATE_APIS + +/* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */ +/*#define MINIZ_NO_INFLATE_APIS */ + +/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_APIS */ + +/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ +#define MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ +#define MINIZ_NO_ZLIB_APIS + +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ +#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. + Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc + callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user + functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ +/*#define MINIZ_NO_MALLOC */ + +#define MINIZ_LITTLE_ENDIAN (SDL_BYTEORDER == SDL_LIL_ENDIAN) + +#endif /* QUAKESPASM-SPECIFIC */ + +#ifdef MINIZ_NO_INFLATE_APIS +#define MINIZ_NO_ARCHIVE_APIS +#endif + +#ifdef MINIZ_NO_DEFLATE_APIS +#define MINIZ_NO_ARCHIVE_WRITING_APIS +#endif + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ +#define MINIZ_NO_TIME +#endif + +#include + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ +#define MINIZ_X86_OR_X64_CPU 1 +#else +#define MINIZ_X86_OR_X64_CPU 0 +#endif + +/* Set MINIZ_LITTLE_ENDIAN only if not set */ +#if !defined(MINIZ_LITTLE_ENDIAN) +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +#else + +#if MINIZ_X86_OR_X64_CPU +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +#endif +#endif + +#if defined(__has_feature) +#if __has_feature(undefined_behavior_sanitizer) +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#else +#define MINIZ_HAS_64BIT_REGISTERS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API Definitions. */ + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ +typedef unsigned long mz_ulong; + +#define MZ_CRC32_INIT (0) +#if 0 +/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ +MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); +#endif + +/* Method */ +#define MZ_DEFLATED 8 + +/* Heap allocation callbacks. +Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "10.2.0" +#define MZ_VERNUM 0xA100 +#define MZ_VER_MAJOR 10 +#define MZ_VER_MINOR 2 +#define MZ_VER_REVISION 0 +#define MZ_VER_SUBREVISION 0 + +#ifdef __cplusplus +} +#endif + +#include +#include +#include + +/* ------------------- Types and macros */ +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +#if defined(_MSC_VER) && (_MSC_VER < 1600) +typedef signed __int64 mz_int64; +typedef unsigned __int64 mz_uint64; +#else +#include +typedef int64_t mz_int64; +typedef uint64_t mz_uint64; +#endif +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +#define MZ_FILE void * + +#define MZ_ASSERT(x) assert(x) + +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) +#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj)) +#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2))) || defined(__clang__) +#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 +extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); +extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); +#endif + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif + + +/* ------------------- Low-level Decompression API Definitions */ + +#ifndef MINIZ_NO_INFLATE_APIS + +#ifdef __cplusplus +extern "C" { +#endif +/* Decompression flags used by tinfl_decompress(). */ +/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ +/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ +/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ +/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +/* Max size of LZ dictionary. */ +#define TINFL_LZ_DICT_SIZE 32768 + +/* Return status. */ +typedef enum { + /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ + /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ + /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, + + /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ + TINFL_STATUS_BAD_PARAM = -3, + + /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ + TINFL_STATUS_ADLER32_MISMATCH = -2, + + /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ + TINFL_STATUS_FAILED = -1, + + /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ + + /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ + /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ + TINFL_STATUS_DONE = 0, + + /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ + /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ + /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + + /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ + /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ + /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ + /* so I may need to add some code to address this. */ + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) \ + do \ + { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +/* Internal/private bits follow. */ +enum +{ + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#else +#define TINFL_USE_64BIT_BITBUF 0 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; + mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; + mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; + mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; + mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; + mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + + +/* ------------------- ZIP archive reading/writing */ + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 +}; + +typedef struct +{ + /* Central directory file index. */ + mz_uint32 m_file_index; + + /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ + mz_uint64 m_central_dir_ofs; + + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; + + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; + + /* File's compressed size. */ + mz_uint64 m_comp_size; + + /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ + mz_uint64 m_uncomp_size; + + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; + + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; + + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; + + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; + + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; + + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, + /*After adding a compressed file, seek back + to local file header and set the correct sizes*/ + MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000 +} mz_zip_flags; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +/* -------- ZIP reading or writing */ + +MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); + +/* Returns the total number of files in the archive. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); + +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); + +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); + +/* MZ_TRUE if the archive file entry is a directory entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); + +/* Returns detailed information about an archive file entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ +MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); + +#ifdef __cplusplus +} +#endif + +#endif /* MINIZ_NO_ARCHIVE_APIS */ + +#endif /* Q_MINIZ_H */ diff --git a/Quake/modelgen.hpp b/Quake/modelgen.hpp new file mode 100644 index 0000000..92dbcf4 --- /dev/null +++ b/Quake/modelgen.hpp @@ -0,0 +1,141 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _MODELGEN_H +#define _MODELGEN_H + +// +// modelgen.h: header file for model generation program +// + +// ********************************************************* +// * This file must be identical in the modelgen directory * +// * and in the Quake directory, because it's used to * +// * pass data from one to the other via model files. * +// ********************************************************* + +#ifdef INCLUDELIBS + +#include +#include +#include +#include + +#include "cmdlib.h" +#include "scriplib.h" +#include "trilib.h" +#include "lbmlib.h" +#include "mathlib.h" + +#endif + +#define ALIAS_VERSION 6 + +#define ALIAS_ONSEAM 0x0020 + +// must match definition in spritegn.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum {ST_SYNC=0, ST_RAND } synctype_t; +#endif + +typedef enum { ALIAS_SINGLE=0, ALIAS_GROUP } aliasframetype_t; + +typedef enum { ALIAS_SKIN_SINGLE=0, ALIAS_SKIN_GROUP } aliasskintype_t; + +typedef struct { + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} mdl_t; + +// TODO: could be shorts + +typedef struct { + int onseam; + int s; + int t; +} stvert_t; + +typedef struct dtriangle_s { + int facesfront; + int vertindex[3]; +} dtriangle_t; + +#define DT_FACES_FRONT 0x0010 + +// This mirrors trivert_t in trilib.h, is present so Quake knows how to +// load this data + +typedef struct { + byte v[3]; + byte lightnormalindex; +} trivertx_t; + +typedef struct { + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used + char name[16]; // frame name from grabbing +} daliasframe_t; + +typedef struct { + int numframes; + trivertx_t bboxmin; // lightnormal isn't used + trivertx_t bboxmax; // lightnormal isn't used +} daliasgroup_t; + +typedef struct { + int numskins; +} daliasskingroup_t; + +typedef struct { + float interval; +} daliasinterval_t; + +typedef struct { + float interval; +} daliasskininterval_t; + +typedef struct { + aliasframetype_t type; +} daliasframetype_t; + +typedef struct { + aliasskintype_t type; +} daliasskintype_t; + +#define IDPOLYHEADER (('O'<<24)+('P'<<16)+('D'<<8)+'I') + // little-endian "IDPO" + +#endif /* _MODELGEN_H */ + diff --git a/Quake/net.hpp b/Quake/net.hpp new file mode 100644 index 0000000..a078490 --- /dev/null +++ b/Quake/net.hpp @@ -0,0 +1,115 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2009-2010 Ozkan Sezer +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + net.h + quake's interface to the networking layer + network functions and data, common to the + whole engine +*/ + +#ifndef _QUAKE_NET_H +#define _QUAKE_NET_H + + +#define NET_NAMELEN 64 + +#define NET_MAXMESSAGE 64000 /* ericw -- was 32000 */ + +extern int DEFAULTnet_hostport; +extern int net_hostport; + +extern cvar_t hostname; + +extern double net_time; +extern sizebuf_t net_message; +extern int net_activeconnections; + + +void NET_Init (void); +void NET_Shutdown (void); + +struct qsocket_s *NET_CheckNewConnections (void); +// returns a new connection number if there is one pending, else -1 + +struct qsocket_s *NET_Connect (const char *host); +// called by client to connect to a host. Returns -1 if not able to + +double NET_QSocketGetTime (const struct qsocket_s *sock); +const char *NET_QSocketGetAddressString (const struct qsocket_s *sock); + +qboolean NET_CanSendMessage (struct qsocket_s *sock); +// Returns true or false if the given qsocket can currently accept a +// message to be transmitted. + +int NET_GetMessage (struct qsocket_s *sock); +// returns data in net_message sizebuf +// returns 0 if no data is waiting +// returns 1 if a message was received +// returns 2 if an unreliable message was received +// returns -1 if the connection died + +int NET_SendMessage (struct qsocket_s *sock, sizebuf_t *data); +int NET_SendUnreliableMessage (struct qsocket_s *sock, sizebuf_t *data); +// returns 0 if the message connot be delivered reliably, but the connection +// is still considered valid +// returns 1 if the message was sent properly +// returns -1 if the connection died + +int NET_SendToAll(sizebuf_t *data, double blocktime); +// This is a reliable *blocking* send to all attached clients. + +void NET_Close (struct qsocket_s *sock); +// if a dead connection is returned by a get or send function, this function +// should be called when it is convenient + +// Server calls when a client is kicked off for a game related misbehavior +// like an illegal protocal conversation. Client calls when disconnecting +// from a server. +// A netcon_t number will not be reused until this function is called for it + +void NET_Poll (void); + + +// Server list related globals: +extern qboolean slistInProgress; +extern qboolean slistSilent; +extern qboolean slistLocal; + +extern int hostCacheCount; + +void NET_Slist_f (void); +void NET_SlistSort (void); +const char *NET_SlistPrintServer (int n); +const char *NET_SlistPrintServerName (int n); + + +/* FIXME: driver related, but public: + */ +extern qboolean ipxAvailable; +extern qboolean tcpipAvailable; +extern char my_ipx_address[NET_NAMELEN]; +extern char my_tcpip_address[NET_NAMELEN]; + +#endif /* _QUAKE_NET_H */ + diff --git a/Quake/net_bsd.cpp b/Quake/net_bsd.cpp new file mode 100644 index 0000000..ad71637 --- /dev/null +++ b/Quake/net_bsd.cpp @@ -0,0 +1,96 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "q_stdinc.hpp" +#include "arch_def.hpp" +#include "net_sys.hpp" +#include "quakedef.hpp" +#include "net_defs.hpp" + +#include "net_dgrm.hpp" +#include "net_loop.hpp" + +net_driver_t net_drivers[] = +{ + { "Loopback", + false, + Loop_Init, + Loop_Listen, + Loop_SearchForHosts, + Loop_Connect, + Loop_CheckNewConnections, + Loop_GetMessage, + Loop_SendMessage, + Loop_SendUnreliableMessage, + Loop_CanSendMessage, + Loop_CanSendUnreliableMessage, + Loop_Close, + Loop_Shutdown + }, + + { "Datagram", + false, + Datagram_Init, + Datagram_Listen, + Datagram_SearchForHosts, + Datagram_Connect, + Datagram_CheckNewConnections, + Datagram_GetMessage, + Datagram_SendMessage, + Datagram_SendUnreliableMessage, + Datagram_CanSendMessage, + Datagram_CanSendUnreliableMessage, + Datagram_Close, + Datagram_Shutdown + } +}; + +const int net_numdrivers = Q_COUNTOF(net_drivers); + +#include "net_udp.hpp" + +net_landriver_t net_landrivers[] = +{ + { "UDP", + false, + 0, + UDP_Init, + UDP_Shutdown, + UDP_Listen, + UDP_OpenSocket, + UDP_CloseSocket, + UDP_Connect, + UDP_CheckNewConnections, + UDP_Read, + UDP_Write, + UDP_Broadcast, + UDP_AddrToString, + UDP_StringToAddr, + UDP_GetSocketAddr, + UDP_GetNameFromAddr, + UDP_GetAddrFromName, + UDP_AddrCompare, + UDP_GetSocketPort, + UDP_SetSocketPort + } +}; + +const int net_numlandrivers = Q_COUNTOF(net_landrivers); diff --git a/Quake/net_defs.hpp b/Quake/net_defs.hpp new file mode 100644 index 0000000..424b85d --- /dev/null +++ b/Quake/net_defs.hpp @@ -0,0 +1,260 @@ +/* + * net_defs.h -- functions and data private to the network layer + * net_sys.h and its dependencies must be included before net_defs.h. + * + * Copyright (C) 1996-1997 Id Software, Inc. + * Copyright (C) 2005-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __NET_DEFS_H +#define __NET_DEFS_H + +struct qsockaddr +{ +#if defined(HAVE_SA_LEN) + unsigned char qsa_len; + unsigned char qsa_family; +#else + short qsa_family; +#endif /* BSD, sockaddr */ + unsigned char qsa_data[14]; +}; + +#define NET_HEADERSIZE (2 * sizeof(unsigned int)) +#define NET_DATAGRAMSIZE (MAX_DATAGRAM + NET_HEADERSIZE) + +// NetHeader flags +#define NETFLAG_LENGTH_MASK 0x0000ffff +#define NETFLAG_DATA 0x00010000 +#define NETFLAG_ACK 0x00020000 +#define NETFLAG_NAK 0x00040000 +#define NETFLAG_EOM 0x00080000 +#define NETFLAG_UNRELIABLE 0x00100000 +#define NETFLAG_CTL 0x80000000 + +#if (NETFLAG_LENGTH_MASK & NET_MAXMESSAGE) != NET_MAXMESSAGE +#error "NET_MAXMESSAGE must fit within NETFLAG_LENGTH_MASK" +#endif + +#define NET_PROTOCOL_VERSION 3 + +/** + +This is the network info/connection protocol. It is used to find Quake +servers, get info about them, and connect to them. Once connected, the +Quake game protocol (documented elsewhere) is used. + + +General notes: + game_name is currently always "QUAKE", but is there so this same protocol + can be used for future games as well; can you say Quake2? + +CCREQ_CONNECT + string game_name "QUAKE" + byte net_protocol_version NET_PROTOCOL_VERSION + +CCREQ_SERVER_INFO + string game_name "QUAKE" + byte net_protocol_version NET_PROTOCOL_VERSION + +CCREQ_PLAYER_INFO + byte player_number + +CCREQ_RULE_INFO + string rule + +CCREP_ACCEPT + long port + +CCREP_REJECT + string reason + +CCREP_SERVER_INFO + string server_address + string host_name + string level_name + byte current_players + byte max_players + byte protocol_version NET_PROTOCOL_VERSION + +CCREP_PLAYER_INFO + byte player_number + string name + long colors + long frags + long connect_time + string address + +CCREP_RULE_INFO + string rule + string value + + note: + There are two address forms used above. The short form is just a + port number. The address that goes along with the port is defined as + "whatever address you receive this reponse from". This lets us use + the host OS to solve the problem of multiple host addresses (possibly + with no routing between them); the host will use the right address + when we reply to the inbound connection request. The long from is + a full address and port in a string. It is used for returning the + address of a server that is not running locally. + +**/ + +#define CCREQ_CONNECT 0x01 +#define CCREQ_SERVER_INFO 0x02 +#define CCREQ_PLAYER_INFO 0x03 +#define CCREQ_RULE_INFO 0x04 + +#define CCREP_ACCEPT 0x81 +#define CCREP_REJECT 0x82 +#define CCREP_SERVER_INFO 0x83 +#define CCREP_PLAYER_INFO 0x84 +#define CCREP_RULE_INFO 0x85 + +typedef struct qsocket_s +{ + struct qsocket_s *next; + double connecttime; + double lastMessageTime; + double lastSendTime; + + qboolean disconnected; + qboolean canSend; + qboolean sendNext; + + int driver; + int landriver; + sys_socket_t socket; + void *driverdata; + + unsigned int ackSequence; + unsigned int sendSequence; + unsigned int unreliableSendSequence; + int sendMessageLength; + byte sendMessage [NET_MAXMESSAGE]; + + unsigned int receiveSequence; + unsigned int unreliableReceiveSequence; + int receiveMessageLength; + byte receiveMessage [NET_MAXMESSAGE]; + + struct qsockaddr addr; + char address[NET_NAMELEN]; + +} qsocket_t; + +extern qsocket_t *net_activeSockets; +extern qsocket_t *net_freeSockets; +extern int net_numsockets; + +typedef struct +{ + const char *name; + qboolean initialized; + sys_socket_t controlSock; + sys_socket_t (*Init) (void); + void (*Shutdown) (void); + void (*Listen) (qboolean state); + sys_socket_t (*Open_Socket) (int port); + int (*Close_Socket) (sys_socket_t socketid); + int (*Connect) (sys_socket_t socketid, struct qsockaddr *addr); + sys_socket_t (*CheckNewConnections) (void); + int (*Read) (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); + int (*Write) (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); + int (*Broadcast) (sys_socket_t socketid, byte *buf, int len); + const char * (*AddrToString) (struct qsockaddr *addr); + int (*StringToAddr) (const char *string, struct qsockaddr *addr); + int (*GetSocketAddr) (sys_socket_t socketid, struct qsockaddr *addr); + int (*GetNameFromAddr) (struct qsockaddr *addr, char *name); + int (*GetAddrFromName) (const char *name, struct qsockaddr *addr); + int (*AddrCompare) (struct qsockaddr *addr1, struct qsockaddr *addr2); + int (*GetSocketPort) (struct qsockaddr *addr); + int (*SetSocketPort) (struct qsockaddr *addr, int port); +} net_landriver_t; + +#define MAX_NET_DRIVERS 8 +extern net_landriver_t net_landrivers[]; +extern const int net_numlandrivers; + +typedef struct +{ + const char *name; + qboolean initialized; + int (*Init) (void); + void (*Listen) (qboolean state); + void (*SearchForHosts) (qboolean xmit); + qsocket_t *(*Connect) (const char *host); + qsocket_t *(*CheckNewConnections) (void); + int (*QGetMessage) (qsocket_t *sock); + int (*QSendMessage) (qsocket_t *sock, sizebuf_t *data); + int (*SendUnreliableMessage) (qsocket_t *sock, sizebuf_t *data); + qboolean (*CanSendMessage) (qsocket_t *sock); + qboolean (*CanSendUnreliableMessage) (qsocket_t *sock); + void (*Close) (qsocket_t *sock); + void (*Shutdown) (void); +} net_driver_t; + +extern net_driver_t net_drivers[]; +extern const int net_numdrivers; + +/* Loop driver must always be registered the first */ +#define IS_LOOP_DRIVER(p) ((p) == 0) + +extern int net_driverlevel; + +extern int messagesSent; +extern int messagesReceived; +extern int unreliableMessagesSent; +extern int unreliableMessagesReceived; + +qsocket_t *NET_NewQSocket (void); +void NET_FreeQSocket(qsocket_t *); +double SetNetTime(void); + + +#define HOSTCACHESIZE 8 + +typedef struct +{ + char name[16]; + char map[16]; + char cname[32]; + int users; + int maxusers; + int driver; + int ldriver; + struct qsockaddr addr; +} hostcache_t; + +extern int hostCacheCount; +extern hostcache_t hostcache[HOSTCACHESIZE]; + + +typedef struct _PollProcedure +{ + struct _PollProcedure *next; + double nextTime; + void (*procedure)(void *); + void *arg; +} PollProcedure; + +void SchedulePollProcedure(PollProcedure *pp, double timeOffset); + +#endif /* __NET_DEFS_H */ + diff --git a/Quake/net_dgrm.cpp b/Quake/net_dgrm.cpp new file mode 100644 index 0000000..76f619b --- /dev/null +++ b/Quake/net_dgrm.cpp @@ -0,0 +1,1421 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// This is enables a simple IP banning mechanism +#define BAN_TEST + +#include "q_stdinc.hpp" +#include "arch_def.hpp" +#include "net_sys.hpp" +#include "quakedef.hpp" +#include "net_defs.hpp" +#include "net_dgrm.hpp" + +// these two macros are to make the code more readable +#define sfunc net_landrivers[sock->landriver] +#define dfunc net_landrivers[net_landriverlevel] + +static int net_landriverlevel; + +/* statistic counters */ +static int packetsSent = 0; +static int packetsReSent = 0; +static int packetsReceived = 0; +static int receivedDuplicateCount = 0; +static int shortPacketCount = 0; +static int droppedDatagrams; + +static struct +{ + unsigned int length; + unsigned int sequence; + byte data[MAX_DATAGRAM]; +} packetBuffer; + +static int myDriverLevel; + +extern qboolean m_return_onerror; +extern char m_return_reason[32]; + + +static char *StrAddr (struct qsockaddr *addr) +{ + static char buf[34]; + byte *p = (byte *)addr; + int n; + + for (n = 0; n < 16; n++) + sprintf (buf + n * 2, "%02x", *p++); + return buf; +} + + +#ifdef BAN_TEST + +static struct in_addr banAddr; +static struct in_addr banMask; + +static void NET_Ban_f (void) +{ + char addrStr [32]; + char maskStr [32]; + void (*print_fn)(const char *fmt, ...) FUNCP_PRINTF(1,2); + + if (cmd_source == src_command) + { + if (!sv.active) + { + Cmd_ForwardToServer (); + return; + } + print_fn = Con_Printf; + } + else + { + if (pr_global_struct->deathmatch) + return; + print_fn = SV_ClientPrintf; + } + + switch (Cmd_Argc ()) + { + case 1: + if (banAddr.s_addr != INADDR_ANY) + { + Q_strcpy(addrStr, inet_ntoa(banAddr)); + Q_strcpy(maskStr, inet_ntoa(banMask)); + print_fn("Banning %s [%s]\n", addrStr, maskStr); + } + else + print_fn("Banning not active\n"); + break; + + case 2: + if (q_strcasecmp(Cmd_Argv(1), "off") == 0) + banAddr.s_addr = INADDR_ANY; + else + banAddr.s_addr = inet_addr(Cmd_Argv(1)); + banMask.s_addr = INADDR_NONE; + break; + + case 3: + banAddr.s_addr = inet_addr(Cmd_Argv(1)); + banMask.s_addr = inet_addr(Cmd_Argv(2)); + break; + + default: + print_fn("BAN ip_address [mask]\n"); + break; + } +} +#endif // BAN_TEST + + +int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data) +{ + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + +#ifdef DEBUG + if (data->cursize == 0) + Sys_Error("Datagram_SendMessage: zero length message"); + + if (data->cursize > NET_MAXMESSAGE) + Sys_Error("Datagram_SendMessage: message too big: %u", data->cursize); + + if (sock->canSend == false) + Sys_Error("SendMessage: called with canSend == false"); +#endif + + Q_memcpy(sock->sendMessage, data->data, data->cursize); + sock->sendMessageLength = data->cursize; + + if (data->cursize <= MAX_DATAGRAM) + { + dataLen = data->cursize; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_DATAGRAM; + eom = 0; + } + packetLen = NET_HEADERSIZE + dataLen; + + packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom)); + packetBuffer.sequence = BigLong(sock->sendSequence++); + Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen); + + sock->canSend = false; + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + sock->lastSendTime = net_time; + packetsSent++; + return 1; +} + + +static int SendMessageNext (qsocket_t *sock) +{ + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + + if (sock->sendMessageLength <= MAX_DATAGRAM) + { + dataLen = sock->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_DATAGRAM; + eom = 0; + } + packetLen = NET_HEADERSIZE + dataLen; + + packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom)); + packetBuffer.sequence = BigLong(sock->sendSequence++); + Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen); + + sock->sendNext = false; + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + sock->lastSendTime = net_time; + packetsSent++; + return 1; +} + + +static int ReSendMessage (qsocket_t *sock) +{ + unsigned int packetLen; + unsigned int dataLen; + unsigned int eom; + + if (sock->sendMessageLength <= MAX_DATAGRAM) + { + dataLen = sock->sendMessageLength; + eom = NETFLAG_EOM; + } + else + { + dataLen = MAX_DATAGRAM; + eom = 0; + } + packetLen = NET_HEADERSIZE + dataLen; + + packetBuffer.length = BigLong(packetLen | (NETFLAG_DATA | eom)); + packetBuffer.sequence = BigLong(sock->sendSequence - 1); + Q_memcpy (packetBuffer.data, sock->sendMessage, dataLen); + + sock->sendNext = false; + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + sock->lastSendTime = net_time; + packetsReSent++; + return 1; +} + + +qboolean Datagram_CanSendMessage (qsocket_t *sock) +{ + if (sock->sendNext) + SendMessageNext (sock); + + return sock->canSend; +} + + +qboolean Datagram_CanSendUnreliableMessage (qsocket_t *sock) +{ + return true; +} + + +int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) +{ + int packetLen; + +#ifdef DEBUG + if (data->cursize == 0) + Sys_Error("Datagram_SendUnreliableMessage: zero length message"); + + if (data->cursize > MAX_DATAGRAM) + Sys_Error("Datagram_SendUnreliableMessage: message too big: %u", data->cursize); +#endif + + packetLen = NET_HEADERSIZE + data->cursize; + + packetBuffer.length = BigLong(packetLen | NETFLAG_UNRELIABLE); + packetBuffer.sequence = BigLong(sock->unreliableSendSequence++); + Q_memcpy (packetBuffer.data, data->data, data->cursize); + + if (sfunc.Write (sock->socket, (byte *)&packetBuffer, packetLen, &sock->addr) == -1) + return -1; + + packetsSent++; + return 1; +} + + +int Datagram_GetMessage (qsocket_t *sock) +{ + unsigned int length; + unsigned int flags; + int ret = 0; + struct qsockaddr readaddr; + unsigned int sequence; + unsigned int count; + + if (!sock->canSend) + if ((net_time - sock->lastSendTime) > 1.0) + ReSendMessage (sock); + + while (1) + { + length = (unsigned int) sfunc.Read(sock->socket, (byte *)&packetBuffer, + NET_DATAGRAMSIZE, &readaddr); + + // if ((rand() & 255) > 220) + // continue; + + if (length == 0) + break; + + if (length == (unsigned int)-1) + { + Con_Printf("Read error\n"); + return -1; + } + + if (sfunc.AddrCompare(&readaddr, &sock->addr) != 0) + { + Con_Printf("Forged packet received\n"); + Con_Printf("Expected: %s\n", StrAddr (&sock->addr)); + Con_Printf("Received: %s\n", StrAddr (&readaddr)); + continue; + } + + if (length < NET_HEADERSIZE) + { + shortPacketCount++; + continue; + } + + length = BigLong(packetBuffer.length); + flags = length & (~NETFLAG_LENGTH_MASK); + length &= NETFLAG_LENGTH_MASK; + + if (flags & NETFLAG_CTL) + continue; + + sequence = BigLong(packetBuffer.sequence); + packetsReceived++; + + if (flags & NETFLAG_UNRELIABLE) + { + if (sequence < sock->unreliableReceiveSequence) + { + Con_DPrintf("Got a stale datagram\n"); + ret = 0; + break; + } + if (sequence != sock->unreliableReceiveSequence) + { + count = sequence - sock->unreliableReceiveSequence; + droppedDatagrams += count; + Con_DPrintf("Dropped %u datagram(s)\n", count); + } + sock->unreliableReceiveSequence = sequence + 1; + + length -= NET_HEADERSIZE; + + SZ_Clear (&net_message); + SZ_Write (&net_message, packetBuffer.data, length); + + ret = 2; + break; + } + + if (flags & NETFLAG_ACK) + { + if (sequence != (sock->sendSequence - 1)) + { + Con_DPrintf("Stale ACK received\n"); + continue; + } + if (sequence == sock->ackSequence) + { + sock->ackSequence++; + if (sock->ackSequence != sock->sendSequence) + Con_DPrintf("ack sequencing error\n"); + } + else + { + Con_DPrintf("Duplicate ACK received\n"); + continue; + } + sock->sendMessageLength -= MAX_DATAGRAM; + if (sock->sendMessageLength > 0) + { + memmove (sock->sendMessage, sock->sendMessage + MAX_DATAGRAM, sock->sendMessageLength); + sock->sendNext = true; + } + else + { + sock->sendMessageLength = 0; + sock->canSend = true; + } + continue; + } + + if (flags & NETFLAG_DATA) + { + packetBuffer.length = BigLong(NET_HEADERSIZE | NETFLAG_ACK); + packetBuffer.sequence = BigLong(sequence); + sfunc.Write (sock->socket, (byte *)&packetBuffer, NET_HEADERSIZE, &readaddr); + + if (sequence != sock->receiveSequence) + { + receivedDuplicateCount++; + continue; + } + sock->receiveSequence++; + + length -= NET_HEADERSIZE; + + if (flags & NETFLAG_EOM) + { + SZ_Clear(&net_message); + SZ_Write(&net_message, sock->receiveMessage, sock->receiveMessageLength); + SZ_Write(&net_message, packetBuffer.data, length); + sock->receiveMessageLength = 0; + + ret = 1; + break; + } + + Q_memcpy(sock->receiveMessage + sock->receiveMessageLength, packetBuffer.data, length); + sock->receiveMessageLength += length; + continue; + } + } + + if (sock->sendNext) + SendMessageNext (sock); + + return ret; +} + + +static void PrintStats(qsocket_t *s) +{ + Con_Printf("canSend = %4u \n", s->canSend); + Con_Printf("sendSeq = %4u ", s->sendSequence); + Con_Printf("recvSeq = %4u \n", s->receiveSequence); + Con_Printf("\n"); +} + +static void NET_Stats_f (void) +{ + qsocket_t *s; + + if (Cmd_Argc () == 1) + { + Con_Printf("unreliable messages sent = %i\n", unreliableMessagesSent); + Con_Printf("unreliable messages recv = %i\n", unreliableMessagesReceived); + Con_Printf("reliable messages sent = %i\n", messagesSent); + Con_Printf("reliable messages received = %i\n", messagesReceived); + Con_Printf("packetsSent = %i\n", packetsSent); + Con_Printf("packetsReSent = %i\n", packetsReSent); + Con_Printf("packetsReceived = %i\n", packetsReceived); + Con_Printf("receivedDuplicateCount = %i\n", receivedDuplicateCount); + Con_Printf("shortPacketCount = %i\n", shortPacketCount); + Con_Printf("droppedDatagrams = %i\n", droppedDatagrams); + } + else if (Q_strcmp(Cmd_Argv(1), "*") == 0) + { + for (s = net_activeSockets; s; s = s->next) + PrintStats(s); + for (s = net_freeSockets; s; s = s->next) + PrintStats(s); + } + else + { + for (s = net_activeSockets; s; s = s->next) + { + if (q_strcasecmp(Cmd_Argv(1), s->address) == 0) + break; + } + + if (s == NULL) + { + for (s = net_freeSockets; s; s = s->next) + { + if (q_strcasecmp(Cmd_Argv(1), s->address) == 0) + break; + } + } + + if (s == NULL) + return; + + PrintStats(s); + } +} + + +// recognize ip:port (based on ProQuake) +static const char *Strip_Port (const char *host) +{ + static char noport[MAX_QPATH]; + /* array size as in Host_Connect_f() */ + char *p; + int port; + + if (!host || !*host) + return host; + q_strlcpy (noport, host, sizeof(noport)); + if ((p = Q_strrchr(noport, ':')) == NULL) + return host; + *p++ = '\0'; + port = Q_atoi (p); + if (port > 0 && port < 65536 && port != net_hostport) + { + net_hostport = port; + Con_Printf("Port set to %d\n", net_hostport); + } + return noport; +} + + +static qboolean testInProgress = false; +static int testPollCount; +static int testDriver; +static sys_socket_t testSocket; + +static void Test_Poll (void *); +static PollProcedure testPollProcedure = {NULL, 0.0, Test_Poll}; + +static void Test_Poll (void *unused) +{ + struct qsockaddr clientaddr; + int control; + int len; + char name[32]; + char address[64]; + int colors; + int frags; + int connectTime; + + net_landriverlevel = testDriver; + + while (1) + { + len = dfunc.Read (testSocket, net_message.data, net_message.maxsize, &clientaddr); + if (len < (int) sizeof(int)) + break; + + net_message.cursize = len; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + break; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + break; + if ((control & NETFLAG_LENGTH_MASK) != len) + break; + + if (MSG_ReadByte() != CCREP_PLAYER_INFO) + Sys_Error("Unexpected response to Player Info request\n"); + + MSG_ReadByte(); /* playerNumber */ + Q_strcpy(name, MSG_ReadString()); + colors = MSG_ReadLong(); + frags = MSG_ReadLong(); + connectTime = MSG_ReadLong(); + Q_strcpy(address, MSG_ReadString()); + + Con_Printf("%s\n frags:%3i colors:%d %d time:%d\n %s\n", name, frags, colors >> 4, colors & 0x0f, connectTime / 60, address); + } + + testPollCount--; + if (testPollCount) + { + SchedulePollProcedure(&testPollProcedure, 0.1); + } + else + { + dfunc.Close_Socket(testSocket); + testInProgress = false; + } +} + +static void Test_f (void) +{ + const char *host; + int n; + int maxusers = MAX_SCOREBOARD; + struct qsockaddr sendaddr; + + if (testInProgress) + return; + + host = Strip_Port (Cmd_Argv(1)); + + if (host && hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + { + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + if (hostcache[n].driver != myDriverLevel) + continue; + net_landriverlevel = hostcache[n].ldriver; + maxusers = hostcache[n].maxusers; + Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr)); + break; + } + } + + if (n < hostCacheCount) + goto JustDoIt; + } + + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (!net_landrivers[net_landriverlevel].initialized) + continue; + + // see if we can resolve the host name + if (dfunc.GetAddrFromName(host, &sendaddr) != -1) + break; + } + + if (net_landriverlevel == net_numlandrivers) + { + Con_Printf("Could not resolve %s\n", host); + return; + } + +JustDoIt: + testSocket = dfunc.Open_Socket(0); + if (testSocket == INVALID_SOCKET) + return; + + testInProgress = true; + testPollCount = 20; + testDriver = net_landriverlevel; + + for (n = 0; n < maxusers; n++) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_PLAYER_INFO); + MSG_WriteByte(&net_message, n); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (testSocket, net_message.data, net_message.cursize, &sendaddr); + } + SZ_Clear(&net_message); + SchedulePollProcedure(&testPollProcedure, 0.1); +} + + +static qboolean test2InProgress = false; +static int test2Driver; +static sys_socket_t test2Socket; + +static void Test2_Poll (void *); +static PollProcedure test2PollProcedure = {NULL, 0.0, Test2_Poll}; + +static void Test2_Poll (void *unused) +{ + struct qsockaddr clientaddr; + int control; + int len; + char name[256]; + char value[256]; + + net_landriverlevel = test2Driver; + name[0] = 0; + + len = dfunc.Read (test2Socket, net_message.data, net_message.maxsize, &clientaddr); + if (len < (int) sizeof(int)) + goto Reschedule; + + net_message.cursize = len; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + goto Error; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + goto Error; + if ((control & NETFLAG_LENGTH_MASK) != len) + goto Error; + + if (MSG_ReadByte() != CCREP_RULE_INFO) + goto Error; + + Q_strcpy(name, MSG_ReadString()); + if (name[0] == 0) + goto Done; + Q_strcpy(value, MSG_ReadString()); + + Con_Printf("%-16.16s %-16.16s\n", name, value); + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_RULE_INFO); + MSG_WriteString(&net_message, name); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (test2Socket, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + +Reschedule: + SchedulePollProcedure(&test2PollProcedure, 0.05); + return; + +Error: + Con_Printf("Unexpected response to Rule Info request\n"); +Done: + dfunc.Close_Socket(test2Socket); + test2InProgress = false; + return; +} + +static void Test2_f (void) +{ + const char *host; + int n; + struct qsockaddr sendaddr; + + if (test2InProgress) + return; + + host = Strip_Port (Cmd_Argv(1)); + + if (host && hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + { + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + if (hostcache[n].driver != myDriverLevel) + continue; + net_landriverlevel = hostcache[n].ldriver; + Q_memcpy(&sendaddr, &hostcache[n].addr, sizeof(struct qsockaddr)); + break; + } + } + + if (n < hostCacheCount) + goto JustDoIt; + } + + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (!net_landrivers[net_landriverlevel].initialized) + continue; + + // see if we can resolve the host name + if (dfunc.GetAddrFromName(host, &sendaddr) != -1) + break; + } + + if (net_landriverlevel == net_numlandrivers) + { + Con_Printf("Could not resolve %s\n", host); + return; + } + +JustDoIt: + test2Socket = dfunc.Open_Socket(0); + if (test2Socket == INVALID_SOCKET) + return; + + test2InProgress = true; + test2Driver = net_landriverlevel; + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_RULE_INFO); + MSG_WriteString(&net_message, ""); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (test2Socket, net_message.data, net_message.cursize, &sendaddr); + SZ_Clear(&net_message); + SchedulePollProcedure(&test2PollProcedure, 0.05); +} + + +int Datagram_Init (void) +{ + int i, num_inited; + sys_socket_t csock; + +#ifdef BAN_TEST + banAddr.s_addr = INADDR_ANY; + banMask.s_addr = INADDR_NONE; +#endif + myDriverLevel = net_driverlevel; + + Cmd_AddCommand ("net_stats", NET_Stats_f); + + if (safemode || COM_CheckParm("-nolan")) + return -1; + + num_inited = 0; + for (i = 0; i < net_numlandrivers; i++) + { + csock = net_landrivers[i].Init (); + if (csock == INVALID_SOCKET) + continue; + net_landrivers[i].initialized = true; + net_landrivers[i].controlSock = csock; + num_inited++; + } + + if (num_inited == 0) + return -1; + +#ifdef BAN_TEST + Cmd_AddCommand ("ban", NET_Ban_f); +#endif + Cmd_AddCommand ("test", Test_f); + Cmd_AddCommand ("test2", Test2_f); + + return 0; +} + + +void Datagram_Shutdown (void) +{ + int i; + +// +// shutdown the lan drivers +// + for (i = 0; i < net_numlandrivers; i++) + { + if (net_landrivers[i].initialized) + { + net_landrivers[i].Shutdown (); + net_landrivers[i].initialized = false; + } + } +} + + +void Datagram_Close (qsocket_t *sock) +{ + sfunc.Close_Socket(sock->socket); +} + + +void Datagram_Listen (qboolean state) +{ + int i; + + for (i = 0; i < net_numlandrivers; i++) + { + if (net_landrivers[i].initialized) + net_landrivers[i].Listen (state); + } +} + + +static qsocket_t *_Datagram_CheckNewConnections (void) +{ + struct qsockaddr clientaddr; + struct qsockaddr newaddr; + sys_socket_t newsock; + sys_socket_t acceptsock; + qsocket_t *sock; + qsocket_t *s; + int len; + int command; + int control; + int ret; + + acceptsock = dfunc.CheckNewConnections(); + if (acceptsock == INVALID_SOCKET) + return NULL; + + SZ_Clear(&net_message); + + len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr); + if (len < (int) sizeof(int)) + return NULL; + net_message.cursize = len; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + return NULL; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + return NULL; + if ((control & NETFLAG_LENGTH_MASK) != len) + return NULL; + + command = MSG_ReadByte(); + if (command == CCREQ_SERVER_INFO) + { + if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) + return NULL; + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_SERVER_INFO); + dfunc.GetSocketAddr(acceptsock, &newaddr); + MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); + MSG_WriteString(&net_message, hostname.string); + MSG_WriteString(&net_message, sv.name); + MSG_WriteByte(&net_message, net_activeconnections); + MSG_WriteByte(&net_message, svs.maxclients); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + + if (command == CCREQ_PLAYER_INFO) + { + int playerNumber; + int activeNumber; + int clientNumber; + client_t *client; + + playerNumber = MSG_ReadByte(); + activeNumber = -1; + + for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) + { + if (client->active) + { + activeNumber++; + if (activeNumber == playerNumber) + break; + } + } + + if (clientNumber == svs.maxclients) + return NULL; + + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_PLAYER_INFO); + MSG_WriteByte(&net_message, playerNumber); + MSG_WriteString(&net_message, client->name); + MSG_WriteLong(&net_message, client->colors); + MSG_WriteLong(&net_message, (int)client->edict->v.frags); + MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime)); + MSG_WriteString(&net_message, client->netconnection->address); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + + return NULL; + } + + if (command == CCREQ_RULE_INFO) + { + const char *prevCvarName; + cvar_t *var; + + // find the search start location + prevCvarName = MSG_ReadString(); + var = Cvar_FindVarAfter (prevCvarName, CVAR_SERVERINFO); + + // send the response + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_RULE_INFO); + if (var) + { + MSG_WriteString(&net_message, var->name); + MSG_WriteString(&net_message, var->string); + } + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + + return NULL; + } + + if (command != CCREQ_CONNECT) + return NULL; + + if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) + return NULL; + + if (MSG_ReadByte() != NET_PROTOCOL_VERSION) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_REJECT); + MSG_WriteString(&net_message, "Incompatible version.\n"); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + +#ifdef BAN_TEST + // check for a ban + if (clientaddr.qsa_family == AF_INET) + { + in_addr_t testAddr; + testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr; + if ((testAddr & banMask.s_addr) == banAddr.s_addr) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_REJECT); + MSG_WriteString(&net_message, "You have been banned.\n"); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + } +#endif + + // see if this guy is already connected + for (s = net_activeSockets; s; s = s->next) + { + if (s->driver != net_driverlevel) + continue; + ret = dfunc.AddrCompare(&clientaddr, &s->addr); + if (ret >= 0) + { + // is this a duplicate connection reqeust? + if (ret == 0 && net_time - s->connecttime < 2.0) + { + // yes, so send a duplicate reply + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_ACCEPT); + dfunc.GetSocketAddr(s->socket, &newaddr); + MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + // it's somebody coming back in from a crash/disconnect + // so close the old qsocket and let their retry get them back in + NET_Close(s); + return NULL; + } + } + + // allocate a QSocket + sock = NET_NewQSocket (); + if (sock == NULL) // no room; try to let him know + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_REJECT); + MSG_WriteString(&net_message, "Server is full.\n"); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + return NULL; + } + + // allocate a network socket + newsock = dfunc.Open_Socket(0); + if (newsock == INVALID_SOCKET) + { + NET_FreeQSocket(sock); + return NULL; + } + + // connect to the client + if (dfunc.Connect (newsock, &clientaddr) == -1) + { + dfunc.Close_Socket(newsock); + NET_FreeQSocket(sock); + return NULL; + } + + // everything is allocated, just fill in the details + sock->socket = newsock; + sock->landriver = net_landriverlevel; + sock->addr = clientaddr; + Q_strcpy(sock->address, dfunc.AddrToString(&clientaddr)); + + // send him back the info about the server connection he has been allocated + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREP_ACCEPT); + dfunc.GetSocketAddr(newsock, &newaddr); + MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); +// MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); + SZ_Clear(&net_message); + + return sock; +} + +qsocket_t *Datagram_CheckNewConnections (void) +{ + qsocket_t *ret = NULL; + + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (net_landrivers[net_landriverlevel].initialized) + { + if ((ret = _Datagram_CheckNewConnections ()) != NULL) + break; + } + } + return ret; +} + + +static void _Datagram_SearchForHosts (qboolean xmit) +{ + int ret; + int n; + int i; + struct qsockaddr readaddr; + struct qsockaddr myaddr; + int control; + + dfunc.GetSocketAddr (dfunc.controlSock, &myaddr); + if (xmit) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_SERVER_INFO); + MSG_WriteString(&net_message, "QUAKE"); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize); + SZ_Clear(&net_message); + } + + while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0) + { + if (ret < (int) sizeof(int)) + continue; + net_message.cursize = ret; + + // don't answer our own query + if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0) + continue; + + // is the cache full? + if (hostCacheCount == HOSTCACHESIZE) + continue; + + MSG_BeginReading (); + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + continue; + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + continue; + if ((control & NETFLAG_LENGTH_MASK) != ret) + continue; + + if (MSG_ReadByte() != CCREP_SERVER_INFO) + continue; + + dfunc.GetAddrFromName(MSG_ReadString(), &readaddr); + // search the cache for this server + for (n = 0; n < hostCacheCount; n++) + { + if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0) + break; + } + + // is it already there? + if (n < hostCacheCount) + continue; + + // add it + hostCacheCount++; + Q_strcpy(hostcache[n].name, MSG_ReadString()); + Q_strcpy(hostcache[n].map, MSG_ReadString()); + hostcache[n].users = MSG_ReadByte(); + hostcache[n].maxusers = MSG_ReadByte(); + if (MSG_ReadByte() != NET_PROTOCOL_VERSION) + { + Q_strcpy(hostcache[n].cname, hostcache[n].name); + hostcache[n].cname[14] = 0; + Q_strcpy(hostcache[n].name, "*"); + Q_strcat(hostcache[n].name, hostcache[n].cname); + } + Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr)); + hostcache[n].driver = net_driverlevel; + hostcache[n].ldriver = net_landriverlevel; + Q_strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr)); + + // check for a name conflict + for (i = 0; i < hostCacheCount; i++) + { + if (i == n) + continue; + if (q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0) + { + i = Q_strlen(hostcache[n].name); + if (i < 15 && hostcache[n].name[i-1] > '8') + { + hostcache[n].name[i] = '0'; + hostcache[n].name[i+1] = 0; + } + else + hostcache[n].name[i-1]++; + + i = -1; + } + } + } +} + +void Datagram_SearchForHosts (qboolean xmit) +{ + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (hostCacheCount == HOSTCACHESIZE) + break; + if (net_landrivers[net_landriverlevel].initialized) + _Datagram_SearchForHosts (xmit); + } +} + + +static qsocket_t *_Datagram_Connect (const char *host) +{ + struct qsockaddr sendaddr; + struct qsockaddr readaddr; + qsocket_t *sock; + sys_socket_t newsock; + int ret; + int reps; + double start_time; + int control; + const char *reason; + + // see if we can resolve the host name + if (dfunc.GetAddrFromName(host, &sendaddr) == -1) + { + Con_Printf("Could not resolve %s\n", host); + return NULL; + } + + newsock = dfunc.Open_Socket (0); + if (newsock == INVALID_SOCKET) + return NULL; + + sock = NET_NewQSocket (); + if (sock == NULL) + goto ErrorReturn2; + sock->socket = newsock; + sock->landriver = net_landriverlevel; + + // connect to the host + if (dfunc.Connect (newsock, &sendaddr) == -1) + goto ErrorReturn; + + // send the connection request + Con_Printf("trying...\n"); + SCR_UpdateScreen (); + start_time = net_time; + + for (reps = 0; reps < 3; reps++) + { + SZ_Clear(&net_message); + // save space for the header, filled in later + MSG_WriteLong(&net_message, 0); + MSG_WriteByte(&net_message, CCREQ_CONNECT); + MSG_WriteString(&net_message, "QUAKE"); + MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); + *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); + dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr); + SZ_Clear(&net_message); + do + { + ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr); + // if we got something, validate it + if (ret > 0) + { + // is it from the right place? + if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0) + { + Con_Printf("wrong reply address\n"); + Con_Printf("Expected: %s | %s\n", dfunc.AddrToString (&sendaddr), StrAddr(&sendaddr)); + Con_Printf("Received: %s | %s\n", dfunc.AddrToString (&readaddr), StrAddr(&readaddr)); + SCR_UpdateScreen (); + ret = 0; + continue; + } + + if (ret < (int) sizeof(int)) + { + ret = 0; + continue; + } + + net_message.cursize = ret; + MSG_BeginReading (); + + control = BigLong(*((int *)net_message.data)); + MSG_ReadLong(); + if (control == -1) + { + ret = 0; + continue; + } + if ((control & (~NETFLAG_LENGTH_MASK)) != (int)NETFLAG_CTL) + { + ret = 0; + continue; + } + if ((control & NETFLAG_LENGTH_MASK) != ret) + { + ret = 0; + continue; + } + } + } + while (ret == 0 && (SetNetTime() - start_time) < 2.5); + + if (ret) + break; + + Con_Printf("still trying...\n"); + SCR_UpdateScreen (); + start_time = SetNetTime(); + } + + if (ret == 0) + { + reason = "No Response"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + if (ret == -1) + { + reason = "Network Error"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + ret = MSG_ReadByte(); + if (ret == CCREP_REJECT) + { + reason = MSG_ReadString(); + Con_Printf("%s\n", reason); + q_strlcpy(m_return_reason, reason, sizeof(m_return_reason)); + goto ErrorReturn; + } + + if (ret == CCREP_ACCEPT) + { + Q_memcpy(&sock->addr, &sendaddr, sizeof(struct qsockaddr)); + dfunc.SetSocketPort (&sock->addr, MSG_ReadLong()); + } + else + { + reason = "Bad Response"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + dfunc.GetNameFromAddr (&sendaddr, sock->address); + + Con_Printf ("Connection accepted\n"); + sock->lastMessageTime = SetNetTime(); + + // switch the connection to the specified address + if (dfunc.Connect (newsock, &sock->addr) == -1) + { + reason = "Connect to Game failed"; + Con_Printf("%s\n", reason); + Q_strcpy(m_return_reason, reason); + goto ErrorReturn; + } + + m_return_onerror = false; + return sock; + +ErrorReturn: + NET_FreeQSocket(sock); +ErrorReturn2: + dfunc.Close_Socket(newsock); + if (m_return_onerror) + { + IN_Deactivate(modestate == MS_WINDOWED); + key_dest = key_menu; + m_state = m_return_state; + m_return_onerror = false; + } + return NULL; +} + +qsocket_t *Datagram_Connect (const char *host) +{ + qsocket_t *ret = NULL; + + host = Strip_Port (host); + for (net_landriverlevel = 0; net_landriverlevel < net_numlandrivers; net_landriverlevel++) + { + if (net_landrivers[net_landriverlevel].initialized) + { + if ((ret = _Datagram_Connect (host)) != NULL) + break; + } + } + return ret; +} + diff --git a/Quake/net_dgrm.hpp b/Quake/net_dgrm.hpp new file mode 100644 index 0000000..357a829 --- /dev/null +++ b/Quake/net_dgrm.hpp @@ -0,0 +1,39 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __NET_DATAGRAM_H +#define __NET_DATAGRAM_H + +int Datagram_Init (void); +void Datagram_Listen (qboolean state); +void Datagram_SearchForHosts (qboolean xmit); +qsocket_t *Datagram_Connect (const char *host); +qsocket_t *Datagram_CheckNewConnections (void); +int Datagram_GetMessage (qsocket_t *sock); +int Datagram_SendMessage (qsocket_t *sock, sizebuf_t *data); +int Datagram_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data); +qboolean Datagram_CanSendMessage (qsocket_t *sock); +qboolean Datagram_CanSendUnreliableMessage (qsocket_t *sock); +void Datagram_Close (qsocket_t *sock); +void Datagram_Shutdown (void); + +#endif /* __NET_DATAGRAM_H */ + diff --git a/Quake/net_loop.cpp b/Quake/net_loop.cpp new file mode 100644 index 0000000..b682b98 --- /dev/null +++ b/Quake/net_loop.cpp @@ -0,0 +1,250 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "q_stdinc.hpp" +#include "arch_def.hpp" +#include "net_sys.hpp" +#include "quakedef.hpp" +#include "net_defs.hpp" +#include "net_loop.hpp" + +static qboolean localconnectpending = false; +static qsocket_t *loop_client = NULL; +static qsocket_t *loop_server = NULL; + +int Loop_Init (void) +{ + if (cls.state == ca_dedicated) + return -1; + return 0; +} + + +void Loop_Shutdown (void) +{ +} + + +void Loop_Listen (qboolean state) +{ +} + + +void Loop_SearchForHosts (qboolean xmit) +{ + if (!sv.active) + return; + + hostCacheCount = 1; + if (Q_strcmp(hostname.string, "UNNAMED") == 0) + Q_strcpy(hostcache[0].name, "local"); + else + Q_strcpy(hostcache[0].name, hostname.string); + Q_strcpy(hostcache[0].map, sv.name); + hostcache[0].users = net_activeconnections; + hostcache[0].maxusers = svs.maxclients; + hostcache[0].driver = net_driverlevel; + Q_strcpy(hostcache[0].cname, "local"); +} + + +qsocket_t *Loop_Connect (const char *host) +{ + if (Q_strcmp(host,"local") != 0) + return NULL; + + localconnectpending = true; + + if (!loop_client) + { + if ((loop_client = NET_NewQSocket ()) == NULL) + { + Con_Printf("Loop_Connect: no qsocket available\n"); + return NULL; + } + Q_strcpy (loop_client->address, "localhost"); + } + loop_client->receiveMessageLength = 0; + loop_client->sendMessageLength = 0; + loop_client->canSend = true; + + if (!loop_server) + { + if ((loop_server = NET_NewQSocket ()) == NULL) + { + Con_Printf("Loop_Connect: no qsocket available\n"); + return NULL; + } + Q_strcpy (loop_server->address, "LOCAL"); + } + loop_server->receiveMessageLength = 0; + loop_server->sendMessageLength = 0; + loop_server->canSend = true; + + loop_client->driverdata = (void *)loop_server; + loop_server->driverdata = (void *)loop_client; + + return loop_client; +} + + +qsocket_t *Loop_CheckNewConnections (void) +{ + if (!localconnectpending) + return NULL; + + localconnectpending = false; + loop_server->sendMessageLength = 0; + loop_server->receiveMessageLength = 0; + loop_server->canSend = true; + loop_client->sendMessageLength = 0; + loop_client->receiveMessageLength = 0; + loop_client->canSend = true; + return loop_server; +} + + +static int IntAlign(int value) +{ + return (value + (sizeof(int) - 1)) & (~(sizeof(int) - 1)); +} + + +int Loop_GetMessage (qsocket_t *sock) +{ + int ret; + int length; + + if (sock->receiveMessageLength == 0) + return 0; + + ret = sock->receiveMessage[0]; + length = sock->receiveMessage[1] + (sock->receiveMessage[2] << 8); + // alignment byte skipped here + SZ_Clear (&net_message); + SZ_Write (&net_message, &sock->receiveMessage[4], length); + + length = IntAlign(length + 4); + sock->receiveMessageLength -= length; + + if (sock->receiveMessageLength) + memmove (sock->receiveMessage, &sock->receiveMessage[length], sock->receiveMessageLength); + + if (sock->driverdata && ret == 1) + ((qsocket_t *)sock->driverdata)->canSend = true; + + return ret; +} + + +int Loop_SendMessage (qsocket_t *sock, sizebuf_t *data) +{ + byte *buffer; + int *bufferLength; + + if (!sock->driverdata) + return -1; + + bufferLength = &((qsocket_t *)sock->driverdata)->receiveMessageLength; + + if ((*bufferLength + data->cursize + 4) > NET_MAXMESSAGE) + Sys_Error("Loop_SendMessage: overflow"); + + buffer = ((qsocket_t *)sock->driverdata)->receiveMessage + *bufferLength; + + // message type + *buffer++ = 1; + + // length + *buffer++ = data->cursize & 0xff; + *buffer++ = data->cursize >> 8; + + // align + buffer++; + + // message + Q_memcpy(buffer, data->data, data->cursize); + *bufferLength = IntAlign(*bufferLength + data->cursize + 4); + + sock->canSend = false; + return 1; +} + + +int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) +{ + byte *buffer; + int *bufferLength; + + if (!sock->driverdata) + return -1; + + bufferLength = &((qsocket_t *)sock->driverdata)->receiveMessageLength; + + if ((*bufferLength + data->cursize + sizeof(byte) + sizeof(short)) > NET_MAXMESSAGE) + return 0; + + buffer = ((qsocket_t *)sock->driverdata)->receiveMessage + *bufferLength; + + // message type + *buffer++ = 2; + + // length + *buffer++ = data->cursize & 0xff; + *buffer++ = data->cursize >> 8; + + // align + buffer++; + + // message + Q_memcpy(buffer, data->data, data->cursize); + *bufferLength = IntAlign(*bufferLength + data->cursize + 4); + return 1; +} + + +qboolean Loop_CanSendMessage (qsocket_t *sock) +{ + if (!sock->driverdata) + return false; + return sock->canSend; +} + + +qboolean Loop_CanSendUnreliableMessage (qsocket_t *sock) +{ + return true; +} + + +void Loop_Close (qsocket_t *sock) +{ + if (sock->driverdata) + ((qsocket_t *)sock->driverdata)->driverdata = NULL; + sock->receiveMessageLength = 0; + sock->sendMessageLength = 0; + sock->canSend = true; + if (sock == loop_client) + loop_client = NULL; + else + loop_server = NULL; +} + diff --git a/Quake/net_loop.hpp b/Quake/net_loop.hpp new file mode 100644 index 0000000..267193d --- /dev/null +++ b/Quake/net_loop.hpp @@ -0,0 +1,40 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __NET_LOOP_H +#define __NET_LOOP_H + +// net_loop.h +int Loop_Init (void); +void Loop_Listen (qboolean state); +void Loop_SearchForHosts (qboolean xmit); +qsocket_t *Loop_Connect (const char *host); +qsocket_t *Loop_CheckNewConnections (void); +int Loop_GetMessage (qsocket_t *sock); +int Loop_SendMessage (qsocket_t *sock, sizebuf_t *data); +int Loop_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data); +qboolean Loop_CanSendMessage (qsocket_t *sock); +qboolean Loop_CanSendUnreliableMessage (qsocket_t *sock); +void Loop_Close (qsocket_t *sock); +void Loop_Shutdown (void); + +#endif /* __NET_LOOP_H */ + diff --git a/Quake/net_main.cpp b/Quake/net_main.cpp new file mode 100644 index 0000000..1395339 --- /dev/null +++ b/Quake/net_main.cpp @@ -0,0 +1,906 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "q_stdinc.hpp" +#include "arch_def.hpp" +#include "net_sys.hpp" +#include "quakedef.hpp" +#include "net_defs.hpp" + +qsocket_t *net_activeSockets = NULL; +qsocket_t *net_freeSockets = NULL; +int net_numsockets = 0; + +qboolean ipxAvailable = false; +qboolean tcpipAvailable = false; + +int net_hostport; +int DEFAULTnet_hostport = 26000; + +char my_ipx_address[NET_NAMELEN]; +char my_tcpip_address[NET_NAMELEN]; + +static qboolean listening = false; + +qboolean slistInProgress = false; +qboolean slistSilent = false; +qboolean slistLocal = true; +static double slistStartTime; +static int slistLastShown; + +static void Slist_Send (void *); +static void Slist_Poll (void *); +static PollProcedure slistSendProcedure = {NULL, 0.0, Slist_Send}; +static PollProcedure slistPollProcedure = {NULL, 0.0, Slist_Poll}; + +sizebuf_t net_message; +int net_activeconnections = 0; + +int messagesSent = 0; +int messagesReceived = 0; +int unreliableMessagesSent = 0; +int unreliableMessagesReceived = 0; + +static cvar_t net_messagetimeout = {"net_messagetimeout","300",CVAR_NONE}; +cvar_t hostname = {"hostname", "UNNAMED", CVAR_NONE}; + +// these two macros are to make the code more readable +#define sfunc net_drivers[sock->driver] +#define dfunc net_drivers[net_driverlevel] + +int net_driverlevel; + +double net_time; + + +double SetNetTime (void) +{ + net_time = Sys_DoubleTime(); + return net_time; +} + + +/* +=================== +NET_NewQSocket + +Called by drivers when a new communications endpoint is required +The sequence and buffer fields will be filled in properly +=================== +*/ +qsocket_t *NET_NewQSocket (void) +{ + qsocket_t *sock; + + if (net_freeSockets == NULL) + return NULL; + + if (net_activeconnections >= svs.maxclients) + return NULL; + + // get one from free list + sock = net_freeSockets; + net_freeSockets = sock->next; + + // add it to active list + sock->next = net_activeSockets; + net_activeSockets = sock; + + sock->disconnected = false; + sock->connecttime = net_time; + Q_strcpy (sock->address,"UNSET ADDRESS"); + sock->driver = net_driverlevel; + sock->socket = 0; + sock->driverdata = NULL; + sock->canSend = true; + sock->sendNext = false; + sock->lastMessageTime = net_time; + sock->ackSequence = 0; + sock->sendSequence = 0; + sock->unreliableSendSequence = 0; + sock->sendMessageLength = 0; + sock->receiveSequence = 0; + sock->unreliableReceiveSequence = 0; + sock->receiveMessageLength = 0; + + return sock; +} + + +void NET_FreeQSocket(qsocket_t *sock) +{ + qsocket_t *s; + + // remove it from active list + if (sock == net_activeSockets) + net_activeSockets = net_activeSockets->next; + else + { + for (s = net_activeSockets; s; s = s->next) + { + if (s->next == sock) + { + s->next = sock->next; + break; + } + } + + if (!s) + Sys_Error ("NET_FreeQSocket: not active"); + } + + // add it to free list + sock->next = net_freeSockets; + net_freeSockets = sock; + sock->disconnected = true; +} + + +double NET_QSocketGetTime (const qsocket_t *s) +{ + return s->connecttime; +} + + +const char *NET_QSocketGetAddressString (const qsocket_t *s) +{ + return s->address; +} + + +static void NET_Listen_f (void) +{ + if (Cmd_Argc () != 2) + { + Con_Printf ("\"listen\" is \"%d\"\n", listening ? 1 : 0); + return; + } + + listening = Q_atoi(Cmd_Argv(1)) ? true : false; + + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == false) + continue; + dfunc.Listen (listening); + } +} + + +static void MaxPlayers_f (void) +{ + int n; + + if (Cmd_Argc () != 2) + { + Con_Printf ("\"maxplayers\" is \"%d\"\n", svs.maxclients); + return; + } + + if (sv.active) + { + Con_Printf ("maxplayers can not be changed while a server is running.\n"); + return; + } + + n = Q_atoi(Cmd_Argv(1)); + if (n < 1) + n = 1; + if (n > svs.maxclientslimit) + { + n = svs.maxclientslimit; + Con_Printf ("\"maxplayers\" set to \"%d\"\n", n); + } + + if ((n == 1) && listening) + Cbuf_AddText ("listen 0\n"); + + if ((n > 1) && (!listening)) + Cbuf_AddText ("listen 1\n"); + + svs.maxclients = n; + if (n == 1) + Cvar_Set ("deathmatch", "0"); + else + Cvar_Set ("deathmatch", "1"); +} + + +static void NET_Port_f (void) +{ + int n; + + if (Cmd_Argc () != 2) + { + Con_Printf ("\"port\" is \"%d\"\n", net_hostport); + return; + } + + n = Q_atoi(Cmd_Argv(1)); + if (n < 1 || n > 65534) + { + Con_Printf ("Bad value, must be between 1 and 65534\n"); + return; + } + + DEFAULTnet_hostport = n; + net_hostport = n; + + if (listening) + { + // force a change to the new port + Cbuf_AddText ("listen 0\n"); + Cbuf_AddText ("listen 1\n"); + } +} + + +static void PrintSlistHeader(void) +{ + Con_Printf("Server Map Users\n"); + Con_Printf("--------------- --------------- -----\n"); + slistLastShown = 0; +} + + +static void PrintSlist(void) +{ + int n; + + for (n = slistLastShown; n < hostCacheCount; n++) + { + if (hostcache[n].maxusers) + Con_Printf("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name, hostcache[n].map, hostcache[n].users, hostcache[n].maxusers); + else + Con_Printf("%-15.15s %-15.15s\n", hostcache[n].name, hostcache[n].map); + } + slistLastShown = n; +} + + +static void PrintSlistTrailer(void) +{ + if (hostCacheCount) + Con_Printf("== end list ==\n\n"); + else + Con_Printf("No Quake servers found.\n\n"); +} + + +void NET_Slist_f (void) +{ + if (slistInProgress) + return; + + if (! slistSilent) + { + Con_Printf("Looking for Quake servers...\n"); + PrintSlistHeader(); + } + + slistInProgress = true; + slistStartTime = Sys_DoubleTime(); + + SchedulePollProcedure(&slistSendProcedure, 0.0); + SchedulePollProcedure(&slistPollProcedure, 0.1); + + hostCacheCount = 0; +} + + +void NET_SlistSort (void) +{ + if (hostCacheCount > 1) + { + int i, j; + hostcache_t temp; + for (i = 0; i < hostCacheCount; i++) + { + for (j = i + 1; j < hostCacheCount; j++) + { + if (strcmp(hostcache[j].name, hostcache[i].name) < 0) + { + memcpy(&temp, &hostcache[j], sizeof(hostcache_t)); + memcpy(&hostcache[j], &hostcache[i], sizeof(hostcache_t)); + memcpy(&hostcache[i], &temp, sizeof(hostcache_t)); + } + } + } + } +} + + +const char *NET_SlistPrintServer (int idx) +{ + static char string[64]; + + if (idx < 0 || idx >= hostCacheCount) + return ""; + + if (hostcache[idx].maxusers) + { + q_snprintf(string, sizeof(string), "%-15.15s %-15.15s %2u/%2u\n", + hostcache[idx].name, hostcache[idx].map, + hostcache[idx].users, hostcache[idx].maxusers); + } + else + { + q_snprintf(string, sizeof(string), "%-15.15s %-15.15s\n", + hostcache[idx].name, hostcache[idx].map); + } + + return string; +} + + +const char *NET_SlistPrintServerName (int idx) +{ + if (idx < 0 || idx >= hostCacheCount) + return ""; + return hostcache[idx].cname; +} + + +static void Slist_Send (void *unused) +{ + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (!slistLocal && IS_LOOP_DRIVER(net_driverlevel)) + continue; + if (net_drivers[net_driverlevel].initialized == false) + continue; + dfunc.SearchForHosts (true); + } + + if ((Sys_DoubleTime() - slistStartTime) < 0.5) + SchedulePollProcedure(&slistSendProcedure, 0.75); +} + + +static void Slist_Poll (void *unused) +{ + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (!slistLocal && IS_LOOP_DRIVER(net_driverlevel)) + continue; + if (net_drivers[net_driverlevel].initialized == false) + continue; + dfunc.SearchForHosts (false); + } + + if (! slistSilent) + PrintSlist(); + + if ((Sys_DoubleTime() - slistStartTime) < 1.5) + { + SchedulePollProcedure(&slistPollProcedure, 0.1); + return; + } + + if (! slistSilent) + PrintSlistTrailer(); + slistInProgress = false; + slistSilent = false; + slistLocal = true; +} + + +/* +=================== +NET_Connect +=================== +*/ + +int hostCacheCount = 0; +hostcache_t hostcache[HOSTCACHESIZE]; + +qsocket_t *NET_Connect (const char *host) +{ + qsocket_t *ret; + int n; + int numdrivers = net_numdrivers; + + SetNetTime(); + + if (host && *host == 0) + host = NULL; + + if (host) + { + if (q_strcasecmp (host, "local") == 0) + { + numdrivers = 1; + goto JustDoIt; + } + + if (hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + host = hostcache[n].cname; + break; + } + if (n < hostCacheCount) + goto JustDoIt; + } + } + + slistSilent = host ? true : false; + NET_Slist_f (); + + while (slistInProgress) + NET_Poll(); + + if (host == NULL) + { + if (hostCacheCount != 1) + return NULL; + host = hostcache[0].cname; + Con_Printf("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host); + } + + if (hostCacheCount) + { + for (n = 0; n < hostCacheCount; n++) + { + if (q_strcasecmp (host, hostcache[n].name) == 0) + { + host = hostcache[n].cname; + break; + } + } + } + +JustDoIt: + for (net_driverlevel = 0; net_driverlevel < numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == false) + continue; + ret = dfunc.Connect (host); + if (ret) + return ret; + } + + if (host) + { + Con_Printf("\n"); + PrintSlistHeader(); + PrintSlist(); + PrintSlistTrailer(); + } + + return NULL; +} + + +/* +=================== +NET_CheckNewConnections +=================== +*/ +qsocket_t *NET_CheckNewConnections (void) +{ + qsocket_t *ret; + + SetNetTime(); + + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == false) + continue; + if (!IS_LOOP_DRIVER(net_driverlevel) && listening == false) + continue; + ret = dfunc.CheckNewConnections (); + if (ret) + { + return ret; + } + } + + return NULL; +} + +/* +=================== +NET_Close +=================== +*/ +void NET_Close (qsocket_t *sock) +{ + if (!sock) + return; + + if (sock->disconnected) + return; + + SetNetTime(); + + // call the driver_Close function + sfunc.Close (sock); + + NET_FreeQSocket(sock); +} + + +/* +================= +NET_GetMessage + +If there is a complete message, return it in net_message + +returns 0 if no data is waiting +returns 1 if a message was received +returns -1 if connection is invalid +================= +*/ +int NET_GetMessage (qsocket_t *sock) +{ + int ret; + + if (!sock) + return -1; + + if (sock->disconnected) + { + Con_Printf("NET_GetMessage: disconnected socket\n"); + return -1; + } + + SetNetTime(); + + ret = sfunc.QGetMessage(sock); + + // see if this connection has timed out + if (ret == 0 && !IS_LOOP_DRIVER(sock->driver)) + { + if (net_time - sock->lastMessageTime > net_messagetimeout.value) + { + NET_Close(sock); + return -1; + } + } + + if (ret > 0) + { + if (!IS_LOOP_DRIVER(sock->driver)) + { + sock->lastMessageTime = net_time; + if (ret == 1) + messagesReceived++; + else if (ret == 2) + unreliableMessagesReceived++; + } + } + + return ret; +} + + +/* +================== +NET_SendMessage + +Try to send a complete length+message unit over the reliable stream. +returns 0 if the message cannot be delivered reliably, but the connection + is still considered valid +returns 1 if the message was sent properly +returns -1 if the connection died +================== +*/ +int NET_SendMessage (qsocket_t *sock, sizebuf_t *data) +{ + int r; + + if (!sock) + return -1; + + if (sock->disconnected) + { + Con_Printf("NET_SendMessage: disconnected socket\n"); + return -1; + } + + SetNetTime(); + r = sfunc.QSendMessage(sock, data); + if (r == 1 && !IS_LOOP_DRIVER(sock->driver)) + messagesSent++; + + return r; +} + + +int NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data) +{ + int r; + + if (!sock) + return -1; + + if (sock->disconnected) + { + Con_Printf("NET_SendMessage: disconnected socket\n"); + return -1; + } + + SetNetTime(); + r = sfunc.SendUnreliableMessage(sock, data); + if (r == 1 && !IS_LOOP_DRIVER(sock->driver)) + unreliableMessagesSent++; + + return r; +} + + +/* +================== +NET_CanSendMessage + +Returns true or false if the given qsocket can currently accept a +message to be transmitted. +================== +*/ +qboolean NET_CanSendMessage (qsocket_t *sock) +{ + if (!sock) + return false; + + if (sock->disconnected) + return false; + + SetNetTime(); + + return sfunc.CanSendMessage(sock); +} + + +int NET_SendToAll (sizebuf_t *data, double blocktime) +{ + double start; + int i; + int count = 0; + qboolean msg_init[MAX_SCOREBOARD]; /* did we write the message to the client's connection */ + qboolean msg_sent[MAX_SCOREBOARD]; /* did the msg arrive its destination (canSend state). */ + + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + { + /* + if (!host_client->netconnection) + continue; + if (host_client->active) + */ + if (host_client->netconnection && host_client->active) + { + if (IS_LOOP_DRIVER(host_client->netconnection->driver)) + { + NET_SendMessage(host_client->netconnection, data); + msg_init[i] = true; + msg_sent[i] = true; + continue; + } + count++; + msg_init[i] = false; + msg_sent[i] = false; + } + else + { + msg_init[i] = true; + msg_sent[i] = true; + } + } + + start = Sys_DoubleTime(); + while (count) + { + count = 0; + for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) + { + if (! msg_init[i]) + { + if (NET_CanSendMessage (host_client->netconnection)) + { + msg_init[i] = true; + NET_SendMessage(host_client->netconnection, data); + } + else + { + NET_GetMessage (host_client->netconnection); + } + count++; + continue; + } + + if (! msg_sent[i]) + { + if (NET_CanSendMessage (host_client->netconnection)) + { + msg_sent[i] = true; + } + else + { + NET_GetMessage (host_client->netconnection); + } + count++; + continue; + } + } + if ((Sys_DoubleTime() - start) > blocktime) + break; + } + return count; +} + + +//============================================================================= + +/* +==================== +NET_Init +==================== +*/ + +void NET_Init (void) +{ + int i; + qsocket_t *s; + + i = COM_CheckParm ("-port"); + if (!i) + i = COM_CheckParm ("-udpport"); + if (!i) + i = COM_CheckParm ("-ipxport"); + + if (i) + { + if (i < com_argc-1) + DEFAULTnet_hostport = Q_atoi (com_argv[i+1]); + else + Sys_Error ("NET_Init: you must specify a number after -port"); + } + net_hostport = DEFAULTnet_hostport; + + net_numsockets = svs.maxclientslimit; + if (cls.state != ca_dedicated) + net_numsockets++; + if (COM_CheckParm("-listen") || cls.state == ca_dedicated) + listening = true; + + SetNetTime(); + + for (i = 0; i < net_numsockets; i++) + { + s = (qsocket_t *)Hunk_AllocName(sizeof(qsocket_t), "qsocket"); + s->next = net_freeSockets; + net_freeSockets = s; + s->disconnected = true; + } + + // allocate space for network message buffer + SZ_Alloc (&net_message, NET_MAXMESSAGE); + + Cvar_RegisterVariable (&net_messagetimeout); + Cvar_RegisterVariable (&hostname); + + Cmd_AddCommand ("slist", NET_Slist_f); + Cmd_AddCommand ("listen", NET_Listen_f); + Cmd_AddCommand ("maxplayers", MaxPlayers_f); + Cmd_AddCommand ("port", NET_Port_f); + + // initialize all the drivers + for (i = net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].Init() == -1) + continue; + i++; + net_drivers[net_driverlevel].initialized = true; + if (listening) + net_drivers[net_driverlevel].Listen (true); + } + + /* Loop_Init() returns -1 for dedicated server case, + * therefore the i == 0 check is correct */ + if (i == 0 + && cls.state == ca_dedicated + ) + { + Sys_Error("Network not available!"); + } + + if (*my_ipx_address) + { + Con_DPrintf("IPX address %s\n", my_ipx_address); + } + if (*my_tcpip_address) + { + Con_DPrintf("TCP/IP address %s\n", my_tcpip_address); + } +} + +/* +==================== +NET_Shutdown +==================== +*/ + +void NET_Shutdown (void) +{ + qsocket_t *sock; + + SetNetTime(); + + for (sock = net_activeSockets; sock; sock = sock->next) + NET_Close(sock); + +// +// shutdown the drivers +// + for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++) + { + if (net_drivers[net_driverlevel].initialized == true) + { + net_drivers[net_driverlevel].Shutdown (); + net_drivers[net_driverlevel].initialized = false; + } + } +} + + +static PollProcedure *pollProcedureList = NULL; + +void NET_Poll(void) +{ + PollProcedure *pp; + + SetNetTime(); + + for (pp = pollProcedureList; pp; pp = pp->next) + { + if (pp->nextTime > net_time) + break; + pollProcedureList = pp->next; + pp->procedure(pp->arg); + } +} + + +void SchedulePollProcedure(PollProcedure *proc, double timeOffset) +{ + PollProcedure *pp, *prev; + + proc->nextTime = Sys_DoubleTime() + timeOffset; + for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next) + { + if (pp->nextTime >= proc->nextTime) + break; + prev = pp; + } + + if (prev == NULL) + { + proc->next = pollProcedureList; + pollProcedureList = proc; + return; + } + + proc->next = pp; + prev->next = proc; +} + diff --git a/Quake/net_sys.hpp b/Quake/net_sys.hpp new file mode 100644 index 0000000..bc334dc --- /dev/null +++ b/Quake/net_sys.hpp @@ -0,0 +1,200 @@ +/* + * net_sys.h -- common network system header. + * - depends on arch_def.h + * - may depend on q_stdinc.h + * + * Copyright (C) 2007-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __NET_SYS_H__ +#define __NET_SYS_H__ + +#include +#include +#include +#include + +#if defined(PLATFORM_BSD) || defined(PLATFORM_OSX) || \ + defined(PLATFORM_AMIGA) /* bsdsocket.library */ || \ + defined(__GNU__) /* GNU/Hurd */ || defined(__riscos__) || \ + defined(PLATFORM_HAIKU) /* Haiku OS */ +/* struct sockaddr has unsigned char sa_len as the first member in BSD + * variants and the family member is also an unsigned char instead of an + * unsigned short. This should matter only when PLATFORM_UNIX is defined, + * however, checking for the offset of sa_family in every platform that + * provide a struct sockaddr doesn't hurt either (see down below for the + * compile time asserts.) */ +/* FIXME : GET RID OF THIS ABOMINATION !!! */ +#define HAVE_SA_LEN 1 +#define SA_FAM_OFFSET 1 +#else +#undef HAVE_SA_LEN +#define SA_FAM_OFFSET 0 +#endif /* BSD, sockaddr */ + +/* unix includes and compatibility macros */ +#if defined(PLATFORM_UNIX) || defined(PLATFORM_RISCOS) || defined(PLATFORM_HAIKU) + +#include +#include +#if defined(__sun) || defined(sun) +#include +#include +#endif /* __sunos__ */ +#include +#include +#include +#include +#include + +typedef int sys_socket_t; +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) + +#if defined(__APPLE__) && defined(SO_NKE) && !defined(SO_NOADDRERR) + /* ancient Mac OS X SDKs 10.2 and older are missing socklen_t */ +typedef int socklen_t; /* defining as signed int to match the old api */ +#endif /* ancient OSX SDKs */ + +#define SOCKETERRNO errno +#define ioctlsocket ioctl +#define closesocket close +#define selectsocket select +#define IOCTLARG_P(x) /* (char *) */ x + +#define NET_EWOULDBLOCK EWOULDBLOCK +#define NET_ECONNREFUSED ECONNREFUSED + +#define socketerror(x) strerror((x)) + +/* Verify that we defined HAVE_SA_LEN correctly: */ +COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFFSET); + +#endif /* end of unix stuff */ + + +/* amiga includes and compatibility macros */ +#if defined(PLATFORM_AMIGA) /* Amiga bsdsocket.library */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int sys_socket_t; +#define INVALID_SOCKET (-1) +#define SOCKET_ERROR (-1) + +#if !(defined(__AROS__) || defined(__amigaos4__)) +typedef LONG socklen_t; /* int32_t */ +#endif +#if !defined(__amigaos4__) +#if (LONG_MAX <= 2147483647L) +typedef unsigned long in_addr_t; /* u_int32_t */ +#else +typedef unsigned int in_addr_t; /* u_int32_t */ +#endif +#endif + +#define SOCKETERRNO Errno() +#define ioctlsocket IoctlSocket +#define closesocket CloseSocket +#define selectsocket(_N,_R,_W,_E,_T) \ + WaitSelect((_N),(_R),(_W),(_E),(_T),NULL) +#define IOCTLARG_P(x) (char *) x +#if defined(__amigaos4__) || defined(PLATFORM_AMIGAOS3) +#define inet_ntoa(x) Inet_NtoA(x.s_addr) /* Inet_NtoA(*(ULONG*)&x) */ +#define h_errno Errno() +#endif + +#define NET_EWOULDBLOCK EWOULDBLOCK +#define NET_ECONNREFUSED ECONNREFUSED + +#define socketerror(x) strerror((x)) +/* there is h_errno but no hstrerror() */ +#define hstrerror(x) strerror((x)) + +/* Verify that we defined HAVE_SA_LEN correctly: */ +COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFFSET); + +#endif /* end of amiga bsdsocket.library stuff */ + + +/* windows includes and compatibility macros */ +#if defined(PLATFORM_WINDOWS) + +/* NOTE: winsock[2].h already includes windows.h */ +#if !defined(_USE_WINSOCK2) +#include +#else +#include +#include +#endif + +/* there is no in_addr_t on windows: define it as + the type of the S_addr of in_addr structure */ +typedef u_long in_addr_t; /* uint32_t */ + +/* on windows, socklen_t is to be a winsock2 thing */ +#if !defined(IP_MSFILTER_SIZE) +typedef int socklen_t; +#endif /* socklen_t type */ + +typedef SOCKET sys_socket_t; + +#define selectsocket select +#define IOCTLARG_P(x) /* (u_long *) */ x + +#define SOCKETERRNO WSAGetLastError() +#define NET_EWOULDBLOCK WSAEWOULDBLOCK +#define NET_ECONNREFUSED WSAECONNREFUSED +/* must #include "wsaerror.h" for this : */ +#define socketerror(x) __WSAE_StrError((x)) + +/* Verify that we defined HAVE_SA_LEN correctly: */ +COMPILE_TIME_ASSERT(sockaddr, offsetof(struct sockaddr, sa_family) == SA_FAM_OFFSET); + +#endif /* end of windows stuff */ + + +/* macros which may still be missing */ + +#if !defined(INADDR_NONE) +#define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif /* INADDR_NONE */ + +#if !defined(INADDR_LOOPBACK) +#define INADDR_LOOPBACK ((in_addr_t) 0x7f000001) /* 127.0.0.1 */ +#endif /* INADDR_LOOPBACK */ + + +#if !defined(MAXHOSTNAMELEN) +/* SUSv2 guarantees that `Host names are limited to 255 bytes'. + POSIX 1003.1-2001 guarantees that `Host names (not including + the terminating NUL) are limited to HOST_NAME_MAX bytes'. */ +#define MAXHOSTNAMELEN 256 +#endif /* MAXHOSTNAMELEN */ + + +#endif /* __NET_SYS_H__ */ + diff --git a/Quake/net_udp.cpp b/Quake/net_udp.cpp new file mode 100644 index 0000000..a47bc05 --- /dev/null +++ b/Quake/net_udp.cpp @@ -0,0 +1,480 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "q_stdinc.hpp" +#include "arch_def.hpp" +#include "net_sys.hpp" +#include "quakedef.hpp" +#include "net_defs.hpp" + +static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket; +static sys_socket_t net_broadcastsocket = 0; +static struct sockaddr_in broadcastaddr; + +static in_addr_t myAddr; + +#include "net_udp.hpp" + +//============================================================================= + +sys_socket_t UDP_Init (void) +{ + int err, i; + char *tst; + char buff[MAXHOSTNAMELEN]; + struct hostent *local; + struct qsockaddr addr; + + if (COM_CheckParm ("-noudp")) + return INVALID_SOCKET; + + // determine my name & address + myAddr = htonl(INADDR_LOOPBACK); + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + err = SOCKETERRNO; + Con_SafePrintf("UDP_Init: WARNING: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; +#ifdef PLATFORM_OSX + // ericw -- if our hostname ends in ".local" (a macOS thing), + // don't bother calling gethostbyname(), because it blocks for a few seconds + // and then fails (on my system anyway.) + tst = strstr(buff, ".local"); + if (tst && tst[6] == '\0') + { + Con_SafePrintf("UDP_Init: skipping gethostbyname for %s\n", buff); + } + else +#endif + if (!(local = gethostbyname(buff))) + { + Con_SafePrintf("UDP_Init: WARNING: gethostbyname failed (%s)\n", + hstrerror(h_errno)); + } + else if (local->h_addrtype != AF_INET) + { + Con_SafePrintf("UDP_Init: address from gethostbyname not IPv4\n"); + } + else + { + i = COM_CheckParm ("-ip"); + if (i) + { + if (i < com_argc-1) + { + myAddr = inet_addr(com_argv[i + 1]); + if (myAddr == INADDR_NONE) + Sys_Error ("%s is not a valid IP address", com_argv[i + 1]); + strcpy(my_tcpip_address, com_argv[i + 1]); + } + else + { + Sys_Error ("NET_Init: you must specify an IP address after -ip"); + } + } + else + { + myAddr = *(in_addr_t *)local->h_addr_list[0]; + } + } + } + + if ((net_controlsocket = UDP_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("UDP_Init: Unable to open control socket, UDP disabled\n"); + return INVALID_SOCKET; + } + + broadcastaddr.sin_family = AF_INET; + broadcastaddr.sin_addr.s_addr = INADDR_BROADCAST; + broadcastaddr.sin_port = htons((unsigned short)net_hostport); + + UDP_GetSocketAddr (net_controlsocket, &addr); + strcpy(my_tcpip_address, UDP_AddrToString (&addr)); + tst = strrchr(my_tcpip_address, ':'); + if (tst) *tst = 0; + + Con_SafePrintf("UDP Initialized\n"); + tcpipAvailable = true; + + return net_controlsocket; +} + +//============================================================================= + +void UDP_Shutdown (void) +{ + UDP_Listen (false); + UDP_CloseSocket (net_controlsocket); +} + +//============================================================================= + +void UDP_Listen (qboolean state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != INVALID_SOCKET) + return; + if ((net_acceptsocket = UDP_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("UDP_Listen: Unable to open accept socket"); + return; + } + + // disable listening + if (net_acceptsocket == INVALID_SOCKET) + return; + UDP_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t UDP_OpenSocket (int port) +{ + sys_socket_t newsocket; + struct sockaddr_in address; + int _true = 1; + int err; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("UDP_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + memset(&address, 0, sizeof(struct sockaddr_in)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + return newsocket; + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("UDP_OpenSocket: %s\n", socketerror(err)); + UDP_CloseSocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +int UDP_CloseSocket (sys_socket_t socketid) +{ + if (socketid == net_broadcastsocket) + net_broadcastsocket = 0; + return closesocket (socketid); +} + +//============================================================================= + +/* +============ +PartialIPAddress + +this lets you type only as much of the net address as required, using +the local network components to fill in the rest +============ +*/ +static int PartialIPAddress (const char *in, struct qsockaddr *hostaddr) +{ + char buff[256]; + char *b; + int addr, mask, num, port, run; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') + b++; + + addr = 0; + mask = -1; + while (*b == '.') + { + b++; + num = 0; + run = 0; + while (!( *b < '0' || *b > '9')) + { + num = num*10 + *b++ - '0'; + if (++run > 3) + return -1; + } + if ((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0) + return -1; + if (num < 0 || num > 255) + return -1; + mask <<= 8; + addr = (addr<<8) + num; + } + + if (*b++ == ':') + port = atoi(b); + else + port = net_hostport; + + hostaddr->qsa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((unsigned short)port); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = + (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} + +//============================================================================= + +int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr) +{ + return 0; +} + +//============================================================================= + +sys_socket_t UDP_CheckNewConnections (void) +{ + int available; + struct sockaddr_in from; + socklen_t fromlen; + char buff[1]; + + if (net_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (ioctl (net_acceptsocket, FIONREAD, &available) == -1) + { + int err = SOCKETERRNO; + Sys_Error ("UDP: ioctlsocket (FIONREAD) failed (%s)", socketerror(err)); + } + if (available) + return net_acceptsocket; + // quietly absorb empty packets + recvfrom (net_acceptsocket, buff, 0, 0, (struct sockaddr *) &from, &fromlen); + return INVALID_SOCKET; +} + +//============================================================================= + +int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + int ret; + + ret = recvfrom (socketid, buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) + return 0; + Con_SafePrintf ("UDP_Read, recvfrom: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +static int UDP_MakeSocketBroadcastCapable (sys_socket_t socketid) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socketid, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) + == SOCKET_ERROR) + { + int err = SOCKETERRNO; + Con_SafePrintf ("UDP, setsockopt: %s\n", socketerror(err)); + return -1; + } + net_broadcastsocket = socketid; + + return 0; +} + +//============================================================================= + +int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len) +{ + int ret; + + if (socketid != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + Sys_Error("Attempted to use multiple broadcasts sockets"); + ret = UDP_MakeSocketBroadcastCapable (socketid); + if (ret == -1) + { + Con_Printf("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return UDP_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr); +} + +//============================================================================= + +int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + int ret; + + ret = sendto (socketid, buf, len, 0, (struct sockaddr *)addr, + sizeof(struct qsockaddr)); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK) + return 0; + Con_SafePrintf ("UDP_Write, sendto: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +const char *UDP_AddrToString (struct qsockaddr *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + q_snprintf (buffer, sizeof(buffer), "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, + ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} + +//============================================================================= + +int UDP_StringToAddr (const char *string, struct qsockaddr *addr) +{ + int ha1, ha2, ha3, ha4, hp, ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)hp); + return 0; +} + +//============================================================================= + +int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + in_addr_t a; + + memset(addr, 0, sizeof(struct qsockaddr)); + if (getsockname(socketid, (struct sockaddr *)addr, &addrlen) != 0) + return -1; + + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == htonl(INADDR_LOOPBACK)) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} + +//============================================================================= + +int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hostentry) + { + strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + strcpy (name, UDP_AddrToString (addr)); + return 0; +} + +//============================================================================= + +int UDP_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = + *(in_addr_t *)hostentry->h_addr_list[0]; + + return 0; +} + +//============================================================================= + +int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) +{ + if (addr1->qsa_family != addr2->qsa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != + ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != + ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} + +//============================================================================= + +int UDP_GetSocketPort (struct qsockaddr *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} + + +int UDP_SetSocketPort (struct qsockaddr *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); + return 0; +} + +//============================================================================= + diff --git a/Quake/net_udp.hpp b/Quake/net_udp.hpp new file mode 100644 index 0000000..5df71df --- /dev/null +++ b/Quake/net_udp.hpp @@ -0,0 +1,45 @@ +/* +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __net_udp_h +#define __net_udp_h + +sys_socket_t UDP_Init (void); +void UDP_Shutdown (void); +void UDP_Listen (qboolean state); +sys_socket_t UDP_OpenSocket (int port); +int UDP_CloseSocket (sys_socket_t socketid); +int UDP_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t UDP_CheckNewConnections (void); +int UDP_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int UDP_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int UDP_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *UDP_AddrToString (struct qsockaddr *addr); +int UDP_StringToAddr (const char *string, struct qsockaddr *addr); +int UDP_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int UDP_GetNameFromAddr (struct qsockaddr *addr, char *name); +int UDP_GetAddrFromName (const char *name, struct qsockaddr *addr); +int UDP_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int UDP_GetSocketPort (struct qsockaddr *addr); +int UDP_SetSocketPort (struct qsockaddr *addr, int port); + +#endif /* __net_udp_h */ + diff --git a/Quake/net_win.cpp b/Quake/net_win.cpp new file mode 100644 index 0000000..3029659 --- /dev/null +++ b/Quake/net_win.cpp @@ -0,0 +1,121 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +#include "net_dgrm.h" +#include "net_loop.h" + +net_driver_t net_drivers[] = +{ + { "Loopback", + false, + Loop_Init, + Loop_Listen, + Loop_SearchForHosts, + Loop_Connect, + Loop_CheckNewConnections, + Loop_GetMessage, + Loop_SendMessage, + Loop_SendUnreliableMessage, + Loop_CanSendMessage, + Loop_CanSendUnreliableMessage, + Loop_Close, + Loop_Shutdown + }, + + { "Datagram", + false, + Datagram_Init, + Datagram_Listen, + Datagram_SearchForHosts, + Datagram_Connect, + Datagram_CheckNewConnections, + Datagram_GetMessage, + Datagram_SendMessage, + Datagram_SendUnreliableMessage, + Datagram_CanSendMessage, + Datagram_CanSendUnreliableMessage, + Datagram_Close, + Datagram_Shutdown + } +}; + +const int net_numdrivers = Q_COUNTOF(net_drivers); + + +#include "net_wins.h" +#include "net_wipx.h" + +net_landriver_t net_landrivers[] = +{ + { "Winsock TCPIP", + false, + 0, + WINS_Init, + WINS_Shutdown, + WINS_Listen, + WINS_OpenSocket, + WINS_CloseSocket, + WINS_Connect, + WINS_CheckNewConnections, + WINS_Read, + WINS_Write, + WINS_Broadcast, + WINS_AddrToString, + WINS_StringToAddr, + WINS_GetSocketAddr, + WINS_GetNameFromAddr, + WINS_GetAddrFromName, + WINS_AddrCompare, + WINS_GetSocketPort, + WINS_SetSocketPort + }, + + { "Winsock IPX", + false, + 0, + WIPX_Init, + WIPX_Shutdown, + WIPX_Listen, + WIPX_OpenSocket, + WIPX_CloseSocket, + WIPX_Connect, + WIPX_CheckNewConnections, + WIPX_Read, + WIPX_Write, + WIPX_Broadcast, + WIPX_AddrToString, + WIPX_StringToAddr, + WIPX_GetSocketAddr, + WIPX_GetNameFromAddr, + WIPX_GetAddrFromName, + WIPX_AddrCompare, + WIPX_GetSocketPort, + WIPX_SetSocketPort + } +}; + +const int net_numlandrivers = Q_COUNTOF(net_landrivers); diff --git a/Quake/net_wins.cpp b/Quake/net_wins.cpp new file mode 100644 index 0000000..6be7454 --- /dev/null +++ b/Quake/net_wins.cpp @@ -0,0 +1,544 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include "quakedef.h" +#include "net_defs.h" + +static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket; +static sys_socket_t net_broadcastsocket = 0; +static struct sockaddr_in broadcastaddr; + +static in_addr_t myAddr; + +#include "net_wins.h" + +int winsock_initialized = 0; +WSADATA winsockdata; +#define __wsaerr_static /* not static: used by net_wipx.c too */ +#include "wsaerror.h" + +//============================================================================= + +#if !defined(_USE_WINSOCK2) +static double blocktime; + +static INT_PTR PASCAL FAR BlockingHook (void) +{ + MSG msg; + BOOL ret; + + if ((Sys_DoubleTime() - blocktime) > 2.0) + { + WSACancelBlockingCall(); + return FALSE; + } + + /* get the next message, if any */ + ret = (BOOL) PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + + /* if we got one, process it */ + if (ret) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* TRUE if we got a message */ + return ret; +} +#endif /* ! _USE_WINSOCK2 */ + + +static void WINS_GetLocalAddress (void) +{ + struct hostent *local = NULL; + char buff[MAXHOSTNAMELEN]; + in_addr_t addr; + int err; + + if (myAddr != INADDR_ANY) + return; + + if (gethostname(buff, MAXHOSTNAMELEN) == SOCKET_ERROR) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_GetLocalAddress: WARNING: gethostname failed (%s)\n", + socketerror(err)); + return; + } + + buff[MAXHOSTNAMELEN - 1] = 0; +#ifndef _USE_WINSOCK2 + blocktime = Sys_DoubleTime(); + WSASetBlockingHook(BlockingHook); +#endif + local = gethostbyname(buff); + err = WSAGetLastError(); +#ifndef _USE_WINSOCK2 + WSAUnhookBlockingHook(); +#endif + if (local == NULL) + { + Con_SafePrintf("WINS_GetLocalAddress: gethostbyname failed (%s)\n", + __WSAE_StrError(err)); + return; + } + + myAddr = *(in_addr_t *)local->h_addr_list[0]; + + addr = ntohl(myAddr); + sprintf(my_tcpip_address, "%ld.%ld.%ld.%ld", (addr >> 24) & 0xff, (addr >> 16) & 0xff, (addr >> 8) & 0xff, addr & 0xff); +} + + +sys_socket_t WINS_Init (void) +{ + int i, err; + char buff[MAXHOSTNAMELEN]; + + if (COM_CheckParm ("-noudp")) + return -1; + + if (winsock_initialized == 0) + { + err = WSAStartup(MAKEWORD(1,1), &winsockdata); + if (err != 0) + { + Con_SafePrintf("Winsock initialization failed (%s)\n", + socketerror(err)); + return INVALID_SOCKET; + } + } + winsock_initialized++; + + // determine my name & address + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_Init: WARNING: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; + } + i = COM_CheckParm ("-ip"); + if (i) + { + if (i < com_argc-1) + { + myAddr = inet_addr(com_argv[i + 1]); + if (myAddr == INADDR_NONE) + Sys_Error ("%s is not a valid IP address", com_argv[i + 1]); + strcpy(my_tcpip_address, com_argv[i + 1]); + } + else + { + Sys_Error ("NET_Init: you must specify an IP address after -ip"); + } + } + else + { + myAddr = INADDR_ANY; + strcpy(my_tcpip_address, "INADDR_ANY"); + } + + if ((net_controlsocket = WINS_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("WINS_Init: Unable to open control socket, UDP disabled\n"); + if (--winsock_initialized == 0) + WSACleanup (); + return INVALID_SOCKET; + } + + broadcastaddr.sin_family = AF_INET; + broadcastaddr.sin_addr.s_addr = INADDR_BROADCAST; + broadcastaddr.sin_port = htons((unsigned short)net_hostport); + + Con_SafePrintf("UDP Initialized\n"); + tcpipAvailable = true; + + return net_controlsocket; +} + +//============================================================================= + +void WINS_Shutdown (void) +{ + WINS_Listen (false); + WINS_CloseSocket (net_controlsocket); + if (--winsock_initialized == 0) + WSACleanup (); +} + +//============================================================================= + +void WINS_Listen (qboolean state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != INVALID_SOCKET) + return; + WINS_GetLocalAddress(); + if ((net_acceptsocket = WINS_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("WINS_Listen: Unable to open accept socket"); + return; + } + + // disable listening + if (net_acceptsocket == INVALID_SOCKET) + return; + WINS_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t WINS_OpenSocket (int port) +{ + sys_socket_t newsocket; + struct sockaddr_in address; + u_long _true = 1; + int err; + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("WINS_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + memset(&address, 0, sizeof(struct sockaddr_in)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = myAddr; + address.sin_port = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + return newsocket; + + if (tcpipAvailable) + { + err = SOCKETERRNO; + Sys_Error ("Unable to bind to %s (%s)", + WINS_AddrToString ((struct qsockaddr *) &address), + socketerror(err)); + return INVALID_SOCKET; /* not reached */ + } + /* else: we are still in init phase, no need to error */ + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("WINS_OpenSocket: %s\n", socketerror(err)); + closesocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +int WINS_CloseSocket (sys_socket_t socketid) +{ + if (socketid == net_broadcastsocket) + net_broadcastsocket = 0; + return closesocket (socketid); +} + +//============================================================================= + +/* +============ +PartialIPAddress + +this lets you type only as much of the net address as required, using +the local network components to fill in the rest +============ +*/ +static int PartialIPAddress (const char *in, struct qsockaddr *hostaddr) +{ + char buff[256]; + char *b; + int addr, mask, num, port, run; + + buff[0] = '.'; + b = buff; + strcpy(buff+1, in); + if (buff[1] == '.') + b++; + + addr = 0; + mask = -1; + while (*b == '.') + { + b++; + num = 0; + run = 0; + while (!( *b < '0' || *b > '9')) + { + num = num*10 + *b++ - '0'; + if (++run > 3) + return -1; + } + if ((*b < '0' || *b > '9') && *b != '.' && *b != ':' && *b != 0) + return -1; + if (num < 0 || num > 255) + return -1; + mask <<= 8; + addr = (addr<<8) + num; + } + + if (*b++ == ':') + port = Q_atoi(b); + else + port = net_hostport; + + hostaddr->qsa_family = AF_INET; + ((struct sockaddr_in *)hostaddr)->sin_port = htons((unsigned short)port); + ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr = + (myAddr & htonl(mask)) | htonl(addr); + + return 0; +} + +//============================================================================= + +int WINS_Connect (sys_socket_t socketid, struct qsockaddr *addr) +{ + return 0; +} + +//============================================================================= + +sys_socket_t WINS_CheckNewConnections (void) +{ + char buf[4096]; + + if (net_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (recvfrom (net_acceptsocket, buf, sizeof(buf), MSG_PEEK, NULL, NULL) + != SOCKET_ERROR) + { + return net_acceptsocket; + } + return INVALID_SOCKET; +} + +//============================================================================= + +int WINS_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + int ret; + + ret = recvfrom (socketid, (char *)buf, len, 0, (struct sockaddr *)addr, &addrlen); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) + return 0; + Con_SafePrintf ("WINS_Read, recvfrom: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +static int WINS_MakeSocketBroadcastCapable (sys_socket_t socketid) +{ + int i = 1; + + // make this socket broadcast capable + if (setsockopt(socketid, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) + == SOCKET_ERROR) + { + int err = SOCKETERRNO; + Con_SafePrintf ("UDP, setsockopt: %s\n", socketerror(err)); + return -1; + } + net_broadcastsocket = socketid; + + return 0; +} + +//============================================================================= + +int WINS_Broadcast (sys_socket_t socketid, byte *buf, int len) +{ + int ret; + + if (socketid != net_broadcastsocket) + { + if (net_broadcastsocket != 0) + Sys_Error("Attempted to use multiple broadcasts sockets"); + WINS_GetLocalAddress(); + ret = WINS_MakeSocketBroadcastCapable (socketid); + if (ret == -1) + { + Con_Printf("Unable to make socket broadcast capable\n"); + return ret; + } + } + + return WINS_Write (socketid, buf, len, (struct qsockaddr *)&broadcastaddr); +} + +//============================================================================= + +int WINS_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr) +{ + int ret; + + ret = sendto (socketid, (char *)buf, len, 0, (struct sockaddr *)addr, + sizeof(struct qsockaddr)); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK) + return 0; + Con_SafePrintf ("WINS_Write, sendto: %s\n", socketerror(err)); + } + return ret; +} + +//============================================================================= + +const char *WINS_AddrToString (struct qsockaddr *addr) +{ + static char buffer[22]; + int haddr; + + haddr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr); + sprintf(buffer, "%d.%d.%d.%d:%d", (haddr >> 24) & 0xff, + (haddr >> 16) & 0xff, (haddr >> 8) & 0xff, haddr & 0xff, + ntohs(((struct sockaddr_in *)addr)->sin_port)); + return buffer; +} + +//============================================================================= + +int WINS_StringToAddr (const char *string, struct qsockaddr *addr) +{ + int ha1, ha2, ha3, ha4, hp, ipaddr; + + sscanf(string, "%d.%d.%d.%d:%d", &ha1, &ha2, &ha3, &ha4, &hp); + ipaddr = (ha1 << 24) | (ha2 << 16) | (ha3 << 8) | ha4; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr.s_addr = htonl(ipaddr); + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)hp); + return 0; +} + +//============================================================================= + +int WINS_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + in_addr_t a; + + memset(addr, 0, sizeof(struct qsockaddr)); + getsockname(socketid, (struct sockaddr *)addr, &addrlen); + + a = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if (a == 0 || a == htonl(INADDR_LOOPBACK)) + ((struct sockaddr_in *)addr)->sin_addr.s_addr = myAddr; + + return 0; +} + +//============================================================================= + +int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + struct hostent *hostentry; + + hostentry = gethostbyaddr ((char *)&((struct sockaddr_in *)addr)->sin_addr, + sizeof(struct in_addr), AF_INET); + if (hostentry) + { + Q_strncpy (name, (char *)hostentry->h_name, NET_NAMELEN - 1); + return 0; + } + + Q_strcpy (name, WINS_AddrToString (addr)); + return 0; +} + +//============================================================================= + +int WINS_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + struct hostent *hostentry; + + if (name[0] >= '0' && name[0] <= '9') + return PartialIPAddress (name, addr); + + hostentry = gethostbyname (name); + if (!hostentry) + return -1; + + addr->qsa_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)net_hostport); + ((struct sockaddr_in *)addr)->sin_addr.s_addr = + *(in_addr_t *)hostentry->h_addr_list[0]; + + return 0; +} + +//============================================================================= + +int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) +{ + if (addr1->qsa_family != addr2->qsa_family) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_addr.s_addr != + ((struct sockaddr_in *)addr2)->sin_addr.s_addr) + return -1; + + if (((struct sockaddr_in *)addr1)->sin_port != + ((struct sockaddr_in *)addr2)->sin_port) + return 1; + + return 0; +} + +//============================================================================= + +int WINS_GetSocketPort (struct qsockaddr *addr) +{ + return ntohs(((struct sockaddr_in *)addr)->sin_port); +} + + +int WINS_SetSocketPort (struct qsockaddr *addr, int port) +{ + ((struct sockaddr_in *)addr)->sin_port = htons((unsigned short)port); + return 0; +} + +//============================================================================= + diff --git a/Quake/net_wins.hpp b/Quake/net_wins.hpp new file mode 100644 index 0000000..59abda4 --- /dev/null +++ b/Quake/net_wins.hpp @@ -0,0 +1,45 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __NET_WINSOCK_H +#define __NET_WINSOCK_H + +sys_socket_t WINS_Init (void); +void WINS_Shutdown (void); +void WINS_Listen (qboolean state); +sys_socket_t WINS_OpenSocket (int port); +int WINS_CloseSocket (sys_socket_t socketid); +int WINS_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t WINS_CheckNewConnections (void); +int WINS_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WINS_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WINS_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *WINS_AddrToString (struct qsockaddr *addr); +int WINS_StringToAddr (const char *string, struct qsockaddr *addr); +int WINS_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int WINS_GetNameFromAddr (struct qsockaddr *addr, char *name); +int WINS_GetAddrFromName (const char *name, struct qsockaddr *addr); +int WINS_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int WINS_GetSocketPort (struct qsockaddr *addr); +int WINS_SetSocketPort (struct qsockaddr *addr, int port); + +#endif /* __NET_WINSOCK_H */ + diff --git a/Quake/net_wipx.cpp b/Quake/net_wipx.cpp new file mode 100644 index 0000000..bd375d1 --- /dev/null +++ b/Quake/net_wipx.cpp @@ -0,0 +1,448 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// net_wipx.c + +#include "q_stdinc.h" +#include "arch_def.h" +#include "net_sys.h" +#include +#include "quakedef.h" +#include "net_defs.h" +#include "net_wipx.h" + +extern cvar_t hostname; + +static sys_socket_t net_acceptsocket = INVALID_SOCKET; // socket for fielding new connections +static sys_socket_t net_controlsocket; +static struct sockaddr_ipx broadcastaddr; + +/* externs from net_wins.c: */ +extern int winsock_initialized; +extern WSADATA winsockdata; +extern const char *__WSAE_StrError (int); + +#define IPXSOCKETS 18 +static sys_socket_t ipxsocket[IPXSOCKETS]; +static int sequence[IPXSOCKETS]; + +//============================================================================= + +sys_socket_t WIPX_Init (void) +{ + int i, err; + char *colon; + char buff[MAXHOSTNAMELEN]; + struct qsockaddr addr; + + if (COM_CheckParm ("-noipx")) + return INVALID_SOCKET; + + if (winsock_initialized == 0) + { + err = WSAStartup(MAKEWORD(1,1), &winsockdata); + if (err != 0) + { + Con_SafePrintf("Winsock initialization failed (%s)\n", + socketerror(err)); + return INVALID_SOCKET; + } + } + winsock_initialized++; + + for (i = 0; i < IPXSOCKETS; i++) + ipxsocket[i] = 0; + + // determine my name & address + if (gethostname(buff, MAXHOSTNAMELEN) != 0) + { + err = SOCKETERRNO; + Con_SafePrintf("WIPX_Init: WARNING: gethostname failed (%s)\n", + socketerror(err)); + } + else + { + buff[MAXHOSTNAMELEN - 1] = 0; + } + + if ((net_controlsocket = WIPX_OpenSocket(0)) == INVALID_SOCKET) + { + Con_SafePrintf("WIPX_Init: Unable to open control socket, IPX disabled\n"); + if (--winsock_initialized == 0) + WSACleanup (); + return INVALID_SOCKET; + } + + broadcastaddr.sa_family = AF_IPX; + memset(broadcastaddr.sa_netnum, 0, 4); + memset(broadcastaddr.sa_nodenum, 0xff, 6); + broadcastaddr.sa_socket = htons((unsigned short)net_hostport); + + WIPX_GetSocketAddr (net_controlsocket, &addr); + Q_strcpy(my_ipx_address, WIPX_AddrToString (&addr)); + colon = Q_strrchr (my_ipx_address, ':'); + if (colon) + *colon = 0; + + Con_SafePrintf("IPX Initialized\n"); + ipxAvailable = true; + + return net_controlsocket; +} + +//============================================================================= + +void WIPX_Shutdown (void) +{ + WIPX_Listen (false); + WIPX_CloseSocket (net_controlsocket); + if (--winsock_initialized == 0) + WSACleanup (); +} + +//============================================================================= + +void WIPX_Listen (qboolean state) +{ + // enable listening + if (state) + { + if (net_acceptsocket != INVALID_SOCKET) + return; + if ((net_acceptsocket = WIPX_OpenSocket (net_hostport)) == INVALID_SOCKET) + Sys_Error ("WIPX_Listen: Unable to open accept socket"); + return; + } + + // disable listening + if (net_acceptsocket == INVALID_SOCKET) + return; + WIPX_CloseSocket (net_acceptsocket); + net_acceptsocket = INVALID_SOCKET; +} + +//============================================================================= + +sys_socket_t WIPX_OpenSocket (int port) +{ + int err; + sys_socket_t handle, newsocket; + struct sockaddr_ipx address; + u_long _true = 1; + + for (handle = 0; handle < IPXSOCKETS; handle++) + { + if (ipxsocket[handle] == 0) + break; + } + if (handle == IPXSOCKETS) + { + Con_SafePrintf("WIPX_OpenSocket: Out of free IPX handles.\n"); + return INVALID_SOCKET; + } + + if ((newsocket = socket (AF_IPX, SOCK_DGRAM, NSPROTO_IPX)) == INVALID_SOCKET) + { + err = SOCKETERRNO; + Con_SafePrintf("WIPX_OpenSocket: %s\n", socketerror(err)); + return INVALID_SOCKET; + } + + if (ioctlsocket (newsocket, FIONBIO, &_true) == SOCKET_ERROR) + goto ErrorReturn; + + if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof(_true)) + == SOCKET_ERROR) + goto ErrorReturn; + + address.sa_family = AF_IPX; + memset(address.sa_netnum, 0, 4); + memset(address.sa_nodenum, 0, 6);; + address.sa_socket = htons((unsigned short)port); + if (bind (newsocket, (struct sockaddr *)&address, sizeof(address)) == 0) + { + ipxsocket[handle] = newsocket; + sequence[handle] = 0; + return handle; + } + + if (ipxAvailable) + { + err = SOCKETERRNO; + Sys_Error ("IPX bind failed (%s)", socketerror(err)); + return INVALID_SOCKET; /* not reached */ + } + /* else: we are still in init phase, no need to error */ + +ErrorReturn: + err = SOCKETERRNO; + Con_SafePrintf("WIPX_OpenSocket: %s\n", socketerror(err)); + closesocket (newsocket); + return INVALID_SOCKET; +} + +//============================================================================= + +int WIPX_CloseSocket (sys_socket_t handle) +{ + sys_socket_t socketid = ipxsocket[handle]; + int ret; + + ret = closesocket (socketid); + ipxsocket[handle] = 0; + return ret; +} + +//============================================================================= + +int WIPX_Connect (sys_socket_t handle, struct qsockaddr *addr) +{ + return 0; +} + +//============================================================================= + +sys_socket_t WIPX_CheckNewConnections (void) +{ + u_long available; + + if (net_acceptsocket == INVALID_SOCKET) + return INVALID_SOCKET; + + if (ioctlsocket (ipxsocket[net_acceptsocket], FIONREAD, &available) == SOCKET_ERROR) + { + int err = SOCKETERRNO; + Sys_Error ("WIPX: ioctlsocket (FIONREAD) failed (%s)", socketerror(err)); + } + if (available) + return net_acceptsocket; + return INVALID_SOCKET; +} + +//============================================================================= + +static byte netpacketBuffer[NET_DATAGRAMSIZE + 4]; + +int WIPX_Read (sys_socket_t handle, byte *buf, int len, struct qsockaddr *addr) +{ + socklen_t addrlen = sizeof(struct qsockaddr); + sys_socket_t socketid = ipxsocket[handle]; + int ret; + + ret = recvfrom (socketid, (char *)netpacketBuffer, len+4, 0, (struct sockaddr *)addr, &addrlen); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK || err == NET_ECONNREFUSED) + return 0; + Con_SafePrintf ("WIPX_Read, recvfrom: %s\n", socketerror(err)); + } + + if (ret < 4) + return 0; + + // remove sequence number, it's only needed for DOS IPX + ret -= 4; + memcpy(buf, netpacketBuffer+4, ret); + + return ret; +} + +//============================================================================= + +int WIPX_Broadcast (sys_socket_t handle, byte *buf, int len) +{ + return WIPX_Write (handle, buf, len, (struct qsockaddr *)&broadcastaddr); +} + +//============================================================================= + +int WIPX_Write (sys_socket_t handle, byte *buf, int len, struct qsockaddr *addr) +{ + sys_socket_t socketid = ipxsocket[handle]; + int ret; + + // build packet with sequence number + memcpy(&netpacketBuffer[0], &sequence[handle], 4); + sequence[handle]++; + memcpy(&netpacketBuffer[4], buf, len); + len += 4; + + ret = sendto (socketid, (char *)netpacketBuffer, len, 0, (struct sockaddr *)addr, sizeof(struct qsockaddr)); + if (ret == SOCKET_ERROR) + { + int err = SOCKETERRNO; + if (err == NET_EWOULDBLOCK) + return 0; + Con_SafePrintf ("WIPX_Write, sendto: %s\n", socketerror(err)); + } + + return ret; +} + +//============================================================================= + +const char *WIPX_AddrToString (struct qsockaddr *addr) +{ + static char buf[28]; + + sprintf(buf, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", + ((struct sockaddr_ipx *)addr)->sa_netnum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_netnum[3] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[0] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[1] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[2] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[3] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[4] & 0xff, + ((struct sockaddr_ipx *)addr)->sa_nodenum[5] & 0xff, + ntohs(((struct sockaddr_ipx *)addr)->sa_socket) + ); + return buf; +} + +//============================================================================= + +int WIPX_StringToAddr (const char *string, struct qsockaddr *addr) +{ + int val; + char buf[3]; + + buf[2] = 0; + Q_memset(addr, 0, sizeof(struct qsockaddr)); + addr->qsa_family = AF_IPX; + +#define DO(src,dest) do { \ + buf[0] = string[src]; \ + buf[1] = string[src + 1]; \ + if (sscanf (buf, "%x", &val) != 1) \ + return -1; \ + ((struct sockaddr_ipx *)addr)->dest = val; \ + } while (0) + + DO(0, sa_netnum[0]); + DO(2, sa_netnum[1]); + DO(4, sa_netnum[2]); + DO(6, sa_netnum[3]); + DO(9, sa_nodenum[0]); + DO(11, sa_nodenum[1]); + DO(13, sa_nodenum[2]); + DO(15, sa_nodenum[3]); + DO(17, sa_nodenum[4]); + DO(19, sa_nodenum[5]); +#undef DO + + sscanf (&string[22], "%u", &val); + ((struct sockaddr_ipx *)addr)->sa_socket = htons((unsigned short)val); + + return 0; +} + +//============================================================================= + +int WIPX_GetSocketAddr (sys_socket_t handle, struct qsockaddr *addr) +{ + sys_socket_t socketid = ipxsocket[handle]; + socklen_t addrlen = sizeof(struct qsockaddr); + + Q_memset(addr, 0, sizeof(struct qsockaddr)); + if (getsockname(socketid, (struct sockaddr *)addr, &addrlen) != 0) + { + int err = SOCKETERRNO; + /* FIXME: what action should be taken?... */ + Con_SafePrintf ("WIPX, getsockname: %s\n", socketerror(err)); + } + + return 0; +} + +//============================================================================= + +int WIPX_GetNameFromAddr (struct qsockaddr *addr, char *name) +{ + Q_strcpy(name, WIPX_AddrToString(addr)); + return 0; +} + +//============================================================================= + +int WIPX_GetAddrFromName (const char *name, struct qsockaddr *addr) +{ + int n; + char buf[32]; + + n = Q_strlen(name); + + if (n == 12) + { + sprintf(buf, "00000000:%s:%u", name, net_hostport); + return WIPX_StringToAddr (buf, addr); + } + if (n == 21) + { + sprintf(buf, "%s:%u", name, net_hostport); + return WIPX_StringToAddr (buf, addr); + } + if (n > 21 && n <= 27) + return WIPX_StringToAddr (name, addr); + + return -1; +} + +//============================================================================= + +int WIPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) +{ + if (addr1->qsa_family != addr2->qsa_family) + return -1; + + if (*((struct sockaddr_ipx *)addr1)->sa_netnum && *((struct sockaddr_ipx *)addr2)->sa_netnum) + { + if (memcmp(((struct sockaddr_ipx *)addr1)->sa_netnum, ((struct sockaddr_ipx *)addr2)->sa_netnum, 4) != 0) + return -1; + } + + if (memcmp(((struct sockaddr_ipx *)addr1)->sa_nodenum, ((struct sockaddr_ipx *)addr2)->sa_nodenum, 6) != 0) + return -1; + + if (((struct sockaddr_ipx *)addr1)->sa_socket != ((struct sockaddr_ipx *)addr2)->sa_socket) + return 1; + + return 0; +} + +//============================================================================= + +int WIPX_GetSocketPort (struct qsockaddr *addr) +{ + return ntohs(((struct sockaddr_ipx *)addr)->sa_socket); +} + + +int WIPX_SetSocketPort (struct qsockaddr *addr, int port) +{ + ((struct sockaddr_ipx *)addr)->sa_socket = htons((unsigned short)port); + return 0; +} + +//============================================================================= + diff --git a/Quake/net_wipx.hpp b/Quake/net_wipx.hpp new file mode 100644 index 0000000..e8a24d3 --- /dev/null +++ b/Quake/net_wipx.hpp @@ -0,0 +1,45 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __NET_WINIPX_H +#define __NET_WINIPX_H + +sys_socket_t WIPX_Init (void); +void WIPX_Shutdown (void); +void WIPX_Listen (qboolean state); +sys_socket_t WIPX_OpenSocket (int port); +int WIPX_CloseSocket (sys_socket_t socketid); +int WIPX_Connect (sys_socket_t socketid, struct qsockaddr *addr); +sys_socket_t WIPX_CheckNewConnections (void); +int WIPX_Read (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WIPX_Write (sys_socket_t socketid, byte *buf, int len, struct qsockaddr *addr); +int WIPX_Broadcast (sys_socket_t socketid, byte *buf, int len); +const char *WIPX_AddrToString (struct qsockaddr *addr); +int WIPX_StringToAddr (const char *string, struct qsockaddr *addr); +int WIPX_GetSocketAddr (sys_socket_t socketid, struct qsockaddr *addr); +int WIPX_GetNameFromAddr (struct qsockaddr *addr, char *name); +int WIPX_GetAddrFromName (const char *name, struct qsockaddr *addr); +int WIPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2); +int WIPX_GetSocketPort (struct qsockaddr *addr); +int WIPX_SetSocketPort (struct qsockaddr *addr, int port); + +#endif /* __NET_WINIPX_H */ + diff --git a/Quake/pl_linux.cpp b/Quake/pl_linux.cpp new file mode 100644 index 0000000..3e9f1b6 --- /dev/null +++ b/Quake/pl_linux.cpp @@ -0,0 +1,98 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + +static const Uint8 bmp_bytes[] = +{ +#include "qs_bmp.hpp" +}; + +void PL_SetWindowIcon (void) +{ + SDL_RWops *rwop; + SDL_Surface *icon; + Uint32 colorkey; + + /* SDL_RWFromConstMem() requires SDL >= 1.2.7 */ + rwop = SDL_RWFromConstMem(bmp_bytes, sizeof(bmp_bytes)); + if (rwop == NULL) + return; + icon = SDL_LoadBMP_RW(rwop, 1); + if (icon == NULL) + return; + /* make pure magenta (#ff00ff) tranparent */ + colorkey = SDL_MapRGB(icon->format, 255, 0, 255); +#if defined(USE_SDL2) + SDL_SetColorKey(icon, SDL_TRUE, colorkey); + SDL_SetWindowIcon((SDL_Window*) VID_GetWindow(), icon); +#else + SDL_SetColorKey(icon, SDL_SRCCOLORKEY, colorkey); + SDL_WM_SetIcon(icon, NULL); +#endif + SDL_FreeSurface(icon); +} + +void PL_VID_Shutdown (void) +{ +} + +#define MAX_CLIPBOARDTXT MAXCMDLINE /* 256 */ +char *PL_GetClipboardData (void) +{ + char *data = NULL; +#if defined(USE_SDL2) + char *cliptext = SDL_GetClipboardText(); + + if (cliptext != NULL) + { + size_t size = strlen(cliptext) + 1; + /* this is intended for simple small text copies + * such as an ip address, etc: do chop the size + * here, otherwise we may experience Z_Malloc() + * failures and all other not-oh-so-fun stuff. */ + size = q_min((size_t)(MAX_CLIPBOARDTXT), size); + data = (char *) Z_Malloc((int)size); + q_strlcpy (data, cliptext, size); + } +#endif + + return data; +} + +void PL_ErrorDialog (const char *errorMsg) +{ +#if defined(USE_SDL2) + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Quake Error", errorMsg, NULL); +#endif +} + diff --git a/Quake/pl_osx.m b/Quake/pl_osx.m new file mode 100644 index 0000000..792afd1 --- /dev/null +++ b/Quake/pl_osx.m @@ -0,0 +1,99 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif +#import + +void PL_SetWindowIcon (void) +{ +/* nothing to do on OS X */ +} + +void PL_VID_Shutdown (void) +{ +} + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 +#define NSPasteboardTypeString NSStringPboardType +#endif +#define MAX_CLIPBOARDTXT MAXCMDLINE /* 256 */ +char *PL_GetClipboardData (void) +{ + char *data = NULL; + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + NSArray* types = [pasteboard types]; + + if ([types containsObject: NSPasteboardTypeString]) { + NSString* clipboardString = [pasteboard stringForType: NSPasteboardTypeString]; + if (clipboardString != NULL && [clipboardString length] > 0) { + const char* srcdata = NULL; + +#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* for ppc builds targeting 10.3 and older */ + if ([clipboardString canBeConvertedToEncoding:NSASCIIStringEncoding]) + srcdata = [clipboardString cString]; +#else + srcdata = [clipboardString cStringUsingEncoding: NSASCIIStringEncoding]; +#endif + + if (srcdata != NULL) + { + size_t sz = [clipboardString length] + 1; + sz = q_min((size_t)(MAX_CLIPBOARDTXT), sz); + data = (char *) Z_Malloc((int)sz); + q_strlcpy (data, srcdata, sz); + } + } + } + return data; +} + +#ifndef MAC_OS_X_VERSION_10_12 +#define NSAlertStyleCritical NSCriticalAlertStyle +#endif +void PL_ErrorDialog(const char *errorMsg) +{ +#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) /* ppc builds targeting 10.3 and older */ + NSString* msg = [NSString stringWithCString:errorMsg]; +#else + NSString* msg = [NSString stringWithCString:errorMsg encoding:NSASCIIStringEncoding]; +#endif +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1030 + NSRunCriticalAlertPanel (@"Quake Error", @"%@", @"OK", nil, nil, msg); +#else + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + alert.alertStyle = NSAlertStyleCritical; + alert.messageText = @"Quake Error"; + alert.informativeText = msg; + [alert runModal]; +#endif +} + diff --git a/Quake/pl_win.cpp b/Quake/pl_win.cpp new file mode 100644 index 0000000..486e3fc --- /dev/null +++ b/Quake/pl_win.cpp @@ -0,0 +1,114 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.h" +#include +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#include +#else +#include +#include +#endif +#else +#include "SDL.h" +#include "SDL_syswm.h" +#endif + +static HICON icon; + +void PL_SetWindowIcon (void) +{ + HINSTANCE handle; + SDL_SysWMinfo wminfo; + HWND hwnd; + + handle = GetModuleHandle(NULL); + icon = LoadIcon(handle, "icon"); + + if (!icon) + return; /* no icon in the exe */ + + SDL_VERSION(&wminfo.version); + +#if defined(USE_SDL2) + if (SDL_GetWindowWMInfo((SDL_Window*) VID_GetWindow(), &wminfo) != SDL_TRUE) + return; /* wrong SDL version */ + + hwnd = wminfo.info.win.window; +#else + if (SDL_GetWMInfo(&wminfo) != 1) + return; /* wrong SDL version */ + + hwnd = wminfo.window; +#endif +#ifdef _WIN64 + SetClassLongPtr(hwnd, GCLP_HICON, (LONG_PTR) icon); +#else + SetClassLong(hwnd, GCL_HICON, (LONG) icon); +#endif +} + +void PL_VID_Shutdown (void) +{ + DestroyIcon(icon); +} + +#define MAX_CLIPBOARDTXT MAXCMDLINE /* 256 */ +char *PL_GetClipboardData (void) +{ + char *data = NULL; + char *cliptext; + + if (OpenClipboard(NULL) != 0) + { + HANDLE hClipboardData; + + if ((hClipboardData = GetClipboardData(CF_TEXT)) != NULL) + { + cliptext = (char *) GlobalLock(hClipboardData); + if (cliptext != NULL) + { + size_t size = GlobalSize(hClipboardData) + 1; + /* this is intended for simple small text copies + * such as an ip address, etc: do chop the size + * here, otherwise we may experience Z_Malloc() + * failures and all other not-oh-so-fun stuff. */ + size = q_min((size_t)(MAX_CLIPBOARDTXT), size); + data = (char *) Z_Malloc((int)size); + q_strlcpy (data, cliptext, size); + GlobalUnlock (hClipboardData); + } + } + CloseClipboard (); + } + return data; +} + +void PL_ErrorDialog(const char *errorMsg) +{ + MessageBox (NULL, errorMsg, "Quake Error", + MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); +} + diff --git a/Quake/platform.hpp b/Quake/platform.hpp new file mode 100644 index 0000000..5927a00 --- /dev/null +++ b/Quake/platform.hpp @@ -0,0 +1,48 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_PLATFORM_H +#define _QUAKE_PLATFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* platform dependent way to set the window icon */ +void PL_SetWindowIcon(void); + +/* platform dependent cleanup */ +void PL_VID_Shutdown (void); + +/* retrieve text from the clipboard (returns Z_Malloc()'ed data) */ +char *PL_GetClipboardData (void); + +/* show an error dialog */ +void PL_ErrorDialog(const char *text); + +#ifdef __cplusplus +} +#endif + +#endif /* _QUAKE_PLATFORM_H */ + diff --git a/Quake/pr_cmds.cpp b/Quake/pr_cmds.cpp new file mode 100644 index 0000000..03d416d --- /dev/null +++ b/Quake/pr_cmds.cpp @@ -0,0 +1,1858 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" + +#define STRINGTEMP_BUFFERS 16 +#define STRINGTEMP_LENGTH 1024 +static char pr_string_temp[STRINGTEMP_BUFFERS][STRINGTEMP_LENGTH]; +static byte pr_string_tempindex = 0; + +static char *PR_GetTempString (void) +{ + return pr_string_temp[(STRINGTEMP_BUFFERS-1) & ++pr_string_tempindex]; +} + +#define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string + +/* +=============================================================================== + + BUILT-IN FUNCTIONS + +=============================================================================== +*/ + +static const char* PF_GetStringArg(int idx, void* userdata) +{ + if (userdata) + idx += *(int*)userdata; + if (idx < 0 || idx >= pr_argc) + return ""; + return LOC_GetString(G_STRING(OFS_PARM0 + idx * 3)); +} + +static char *PF_VarString (int first) +{ + int i; + static char out[1024]; + const char *format; + size_t s; + + out[0] = 0; + s = 0; + + if (first >= pr_argc) + return out; + + format = LOC_GetString(G_STRING((OFS_PARM0 + first * 3))); + if (LOC_HasPlaceholders(format)) + { + int offset = first + 1; + s = LOC_Format(format, PF_GetStringArg, &offset, out, sizeof(out)); + } + else + { + for (i = first; i < pr_argc; i++) + { + s = q_strlcat(out, LOC_GetString(G_STRING(OFS_PARM0+i*3)), sizeof(out)); + if (s >= sizeof(out)) + { + Con_Warning("PF_VarString: overflow (string truncated)\n"); + return out; + } + } + } + if (s > 255) + { + if (!dev_overflows.varstring || dev_overflows.varstring + CONSOLE_RESPAM_TIME < realtime) + { + Con_DWarning("PF_VarString: %i characters exceeds standard limit of 255 (max = %d).\n", + (int) s, (int)(sizeof(out) - 1)); + dev_overflows.varstring = realtime; + } + } + return out; +} + + +/* +================= +PF_error + +This is a TERMINAL error, which will kill off the entire server. +Dumps self. + +error(value) +================= +*/ +static void PF_error (void) +{ + char *s; + edict_t *ed; + + s = PF_VarString(0); + Con_Printf ("======SERVER ERROR in %s:\n%s\n", + PR_GetString(pr_xfunction->s_name), s); + ed = PROG_TO_EDICT(pr_global_struct->self); + ED_Print (ed); + + Host_Error ("Program error"); +} + +/* +================= +PF_objerror + +Dumps out self, then an error message. The program is aborted and self is +removed, but the level can continue. + +objerror(value) +================= +*/ +static void PF_objerror (void) +{ + char *s; + edict_t *ed; + + s = PF_VarString(0); + Con_Printf ("======OBJECT ERROR in %s:\n%s\n", + PR_GetString(pr_xfunction->s_name), s); + ed = PROG_TO_EDICT(pr_global_struct->self); + ED_Print (ed); + ED_Free (ed); + + //Host_Error ("Program error"); //johnfitz -- by design, this should not be fatal +} + + + +/* +============== +PF_makevectors + +Writes new values for v_forward, v_up, and v_right based on angles +makevectors(vector) +============== +*/ +static void PF_makevectors (void) +{ + AngleVectors (G_VECTOR(OFS_PARM0), pr_global_struct->v_forward, pr_global_struct->v_right, pr_global_struct->v_up); +} + +/* +================= +PF_setorigin + +This is the only valid way to move an object without using the physics +of the world (setting velocity and waiting). Directly changing origin +will not set internal links correctly, so clipping would be messed up. + +This should be called when an object is spawned, and then only if it is +teleported. + +setorigin (entity, origin) +================= +*/ +static void PF_setorigin (void) +{ + edict_t *e; + float *org; + + e = G_EDICT(OFS_PARM0); + org = G_VECTOR(OFS_PARM1); + VectorCopy (org, e->v.origin); + SV_LinkEdict (e, false); +} + + +static void SetMinMaxSize (edict_t *e, float *minvec, float *maxvec, qboolean rotate) +{ + float *angles; + vec3_t rmin, rmax; + float bounds[2][3]; + float xvector[2], yvector[2]; + float a; + vec3_t base, transformed; + int i, j, k, l; + + for (i = 0; i < 3; i++) + if (minvec[i] > maxvec[i]) + PR_RunError ("backwards mins/maxs"); + + rotate = false; // FIXME: implement rotation properly again + + if (!rotate) + { + VectorCopy (minvec, rmin); + VectorCopy (maxvec, rmax); + } + else + { + // find min / max for rotations + angles = e->v.angles; + + a = angles[1]/180 * M_PI; + + xvector[0] = cos(a); + xvector[1] = sin(a); + yvector[0] = -sin(a); + yvector[1] = cos(a); + + VectorCopy (minvec, bounds[0]); + VectorCopy (maxvec, bounds[1]); + + rmin[0] = rmin[1] = rmin[2] = FLT_MAX; + rmax[0] = rmax[1] = rmax[2] = -FLT_MAX; + + for (i = 0; i <= 1; i++) + { + base[0] = bounds[i][0]; + for (j = 0; j <= 1; j++) + { + base[1] = bounds[j][1]; + for (k = 0; k <= 1; k++) + { + base[2] = bounds[k][2]; + + // transform the point + transformed[0] = xvector[0]*base[0] + yvector[0]*base[1]; + transformed[1] = xvector[1]*base[0] + yvector[1]*base[1]; + transformed[2] = base[2]; + + for (l = 0; l < 3; l++) + { + if (transformed[l] < rmin[l]) + rmin[l] = transformed[l]; + if (transformed[l] > rmax[l]) + rmax[l] = transformed[l]; + } + } + } + } + } + +// set derived values + VectorCopy (rmin, e->v.mins); + VectorCopy (rmax, e->v.maxs); + VectorSubtract (maxvec, minvec, e->v.size); + + SV_LinkEdict (e, false); +} + +/* +================= +PF_setsize + +the size box is rotated by the current angle + +setsize (entity, minvector, maxvector) +================= +*/ +static void PF_setsize (void) +{ + edict_t *e; + float *minvec, *maxvec; + + e = G_EDICT(OFS_PARM0); + minvec = G_VECTOR(OFS_PARM1); + maxvec = G_VECTOR(OFS_PARM2); + SetMinMaxSize (e, minvec, maxvec, false); +} + + +/* +================= +PF_setmodel + +setmodel(entity, model) +================= +*/ +static void PF_setmodel (void) +{ + int i; + const char *m, **check; + qmodel_t *mod; + edict_t *e; + + e = G_EDICT(OFS_PARM0); + m = G_STRING(OFS_PARM1); + +// check to see if model was properly precached + for (i = 0, check = sv.model_precache; *check; i++, check++) + { + if (!strcmp(*check, m)) + break; + } + + if (!*check) + { + PR_RunError ("no precache: %s", m); + } + e->v.model = PR_SetEngineString(*check); + e->v.modelindex = i; //SV_ModelIndex (m); + + mod = sv.models[ (int)e->v.modelindex]; // Mod_ForName (m, true); + + if (mod) + //johnfitz -- correct physics cullboxes for bmodels + { + if (mod->type == mod_brush) + SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true); + else + SetMinMaxSize (e, mod->mins, mod->maxs, true); + } + //johnfitz + else + SetMinMaxSize (e, vec3_origin, vec3_origin, true); +} + +/* +================= +PF_bprint + +broadcast print to everyone on server + +bprint(value) +================= +*/ +static void PF_bprint (void) +{ + char *s; + + s = PF_VarString(0); + SV_BroadcastPrintf ("%s", s); +} + +/* +================= +PF_sprint + +single print to a specific client + +sprint(clientent, value) +================= +*/ +static void PF_sprint (void) +{ + char *s; + client_t *client; + int entnum; + + entnum = G_EDICTNUM(OFS_PARM0); + s = PF_VarString(1); + + if (entnum < 1 || entnum > svs.maxclients) + { + Con_Printf ("tried to sprint to a non-client\n"); + return; + } + + client = &svs.clients[entnum-1]; + + MSG_WriteChar (&client->message,svc_print); + MSG_WriteString (&client->message, s ); +} + + +/* +================= +PF_centerprint + +single print to a specific client + +centerprint(clientent, value) +================= +*/ +static void PF_centerprint (void) +{ + char *s; + client_t *client; + int entnum; + + entnum = G_EDICTNUM(OFS_PARM0); + s = PF_VarString(1); + + if (entnum < 1 || entnum > svs.maxclients) + { + Con_Printf ("tried to sprint to a non-client\n"); + return; + } + + client = &svs.clients[entnum-1]; + + MSG_WriteChar (&client->message,svc_centerprint); + MSG_WriteString (&client->message, s); +} + + +/* +================= +PF_normalize + +vector normalize(vector) +================= +*/ +static void PF_normalize (void) +{ + float *value1; + vec3_t newvalue; + double new_temp; + + value1 = G_VECTOR(OFS_PARM0); + + new_temp = (double)value1[0] * value1[0] + (double)value1[1] * value1[1] + (double)value1[2]*value1[2]; + new_temp = sqrt (new_temp); + + if (new_temp == 0) + newvalue[0] = newvalue[1] = newvalue[2] = 0; + else + { + new_temp = 1 / new_temp; + newvalue[0] = value1[0] * new_temp; + newvalue[1] = value1[1] * new_temp; + newvalue[2] = value1[2] * new_temp; + } + + VectorCopy (newvalue, G_VECTOR(OFS_RETURN)); +} + +/* +================= +PF_vlen + +scalar vlen(vector) +================= +*/ +static void PF_vlen (void) +{ + float *value1; + double new_temp; + + value1 = G_VECTOR(OFS_PARM0); + + new_temp = (double)value1[0] * value1[0] + (double)value1[1] * value1[1] + (double)value1[2]*value1[2]; + new_temp = sqrt(new_temp); + + G_FLOAT(OFS_RETURN) = new_temp; +} + +/* +================= +PF_vectoyaw + +float vectoyaw(vector) +================= +*/ +static void PF_vectoyaw (void) +{ + float *value1; + float yaw; + + value1 = G_VECTOR(OFS_PARM0); + + if (value1[1] == 0 && value1[0] == 0) + yaw = 0; + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + G_FLOAT(OFS_RETURN) = yaw; +} + + +/* +================= +PF_vectoangles + +vector vectoangles(vector) +================= +*/ +static void PF_vectoangles (void) +{ + float *value1; + float forward; + float yaw, pitch; + + value1 = G_VECTOR(OFS_PARM0); + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + G_FLOAT(OFS_RETURN+0) = pitch; + G_FLOAT(OFS_RETURN+1) = yaw; + G_FLOAT(OFS_RETURN+2) = 0; +} + +/* +================= +PF_Random + +Returns a number from 0 <= num < 1 + +random() +================= +*/ +static void PF_random (void) +{ + float num; + + num = (rand() & 0x7fff) / ((float)0x7fff); + + G_FLOAT(OFS_RETURN) = num; +} + +/* +================= +PF_particle + +particle(origin, color, count) +================= +*/ +static void PF_particle (void) +{ + float *org, *dir; + float color; + float count; + + org = G_VECTOR(OFS_PARM0); + dir = G_VECTOR(OFS_PARM1); + color = G_FLOAT(OFS_PARM2); + count = G_FLOAT(OFS_PARM3); + SV_StartParticle (org, dir, color, count); +} + + +/* +================= +PF_ambientsound + +================= +*/ +static void PF_ambientsound (void) +{ + const char *samp, **check; + float *pos; + float vol, attenuation; + int i, soundnum; + int large = false; //johnfitz -- PROTOCOL_FITZQUAKE + + pos = G_VECTOR (OFS_PARM0); + samp = G_STRING(OFS_PARM1); + vol = G_FLOAT(OFS_PARM2); + attenuation = G_FLOAT(OFS_PARM3); + +// check to see if samp was properly precached + for (soundnum = 0, check = sv.sound_precache; *check; check++, soundnum++) + { + if (!strcmp(*check, samp)) + break; + } + + if (!*check) + { + Con_Printf ("no precache: %s\n", samp); + return; + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (soundnum > 255) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; //don't send any info protocol can't support + else + large = true; + } + //johnfitz + + SV_ReserveSignonSpace (17); + +// add an svc_spawnambient command to the level signon packet + + //johnfitz -- PROTOCOL_FITZQUAKE + if (large) + MSG_WriteByte (sv.signon,svc_spawnstaticsound2); + else + MSG_WriteByte (sv.signon,svc_spawnstaticsound); + //johnfitz + + for (i = 0; i < 3; i++) + MSG_WriteCoord(sv.signon, pos[i], sv.protocolflags); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (large) + MSG_WriteShort(sv.signon, soundnum); + else + MSG_WriteByte (sv.signon, soundnum); + //johnfitz + + MSG_WriteByte (sv.signon, vol*255); + MSG_WriteByte (sv.signon, attenuation*64); + +} + +/* +================= +PF_sound + +Each entity can have eight independant sound sources, like voice, +weapon, feet, etc. + +Channel 0 is an auto-allocate channel, the others override anything +already running on that entity/channel pair. + +An attenuation of 0 will play full volume everywhere in the level. +Larger attenuations will drop off. + +================= +*/ +static void PF_sound (void) +{ + const char *sample; + int channel; + edict_t *entity; + int volume; + float attenuation; + + entity = G_EDICT(OFS_PARM0); + channel = G_FLOAT(OFS_PARM1); + sample = G_STRING(OFS_PARM2); + volume = G_FLOAT(OFS_PARM3) * 255; + attenuation = G_FLOAT(OFS_PARM4); + + SV_StartSound (entity, channel, sample, volume, attenuation); +} + +/* +================= +PF_break + +break() +================= +*/ +static void PF_break (void) +{ + Con_Printf ("break statement\n"); + *(int *)-4 = 0; // dump to debugger +// PR_RunError ("break statement"); +} + +/* +================= +PF_traceline + +Used for use tracing and shot targeting +Traces are blocked by bbox and exact bsp entityes, and also slide box entities +if the tryents flag is set. + +traceline (vector1, vector2, tryents) +================= +*/ +static void PF_traceline (void) +{ + float *v1, *v2; + trace_t trace; + int nomonsters; + edict_t *ent; + + v1 = G_VECTOR(OFS_PARM0); + v2 = G_VECTOR(OFS_PARM1); + nomonsters = G_FLOAT(OFS_PARM2); + ent = G_EDICT(OFS_PARM3); + + /* FIXME FIXME FIXME: Why do we hit this with certain progs.dat ?? */ + if (developer.value) { + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || + IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) { + Con_Warning ("NAN in traceline:\nv1(%f %f %f) v2(%f %f %f)\nentity %d\n", + v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], NUM_FOR_EDICT(ent)); + } + } + + if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2])) + v1[0] = v1[1] = v1[2] = 0; + if (IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2])) + v2[0] = v2[1] = v2[2] = 0; + + trace = SV_Move (v1, vec3_origin, vec3_origin, v2, nomonsters, ent); + + pr_global_struct->trace_allsolid = trace.allsolid; + pr_global_struct->trace_startsolid = trace.startsolid; + pr_global_struct->trace_fraction = trace.fraction; + pr_global_struct->trace_inwater = trace.inwater; + pr_global_struct->trace_inopen = trace.inopen; + VectorCopy (trace.endpos, pr_global_struct->trace_endpos); + VectorCopy (trace.plane.normal, pr_global_struct->trace_plane_normal); + pr_global_struct->trace_plane_dist = trace.plane.dist; + if (trace.ent) + pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); + else + pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); +} + +/* +================= +PF_checkpos + +Returns true if the given entity can move to the given position from it's +current position by walking or rolling. +FIXME: make work... +scalar checkpos (entity, vector) +================= +*/ +#if 0 +static void PF_checkpos (void) +{ +} +#endif + +//============================================================================ + +static byte *checkpvs; //ericw -- changed to malloc +static int checkpvs_capacity; + +static int PF_newcheckclient (int check) +{ + int i; + byte *pvs; + edict_t *ent; + mleaf_t *leaf; + vec3_t org; + int pvsbytes; + +// cycle to the next one + + if (check < 1) + check = 1; + if (check > svs.maxclients) + check = svs.maxclients; + + if (check == svs.maxclients) + i = 1; + else + i = check + 1; + + for ( ; ; i++) + { + if (i == svs.maxclients+1) + i = 1; + + ent = EDICT_NUM(i); + + if (i == check) + break; // didn't find anything else + + if (ent->free) + continue; + if (ent->v.health <= 0) + continue; + if ((int)ent->v.flags & FL_NOTARGET) + continue; + + // anything that is a client, or has a client as an enemy + break; + } + +// get the PVS for the entity + VectorAdd (ent->v.origin, ent->v.view_ofs, org); + leaf = Mod_PointInLeaf (org, sv.worldmodel); + pvs = Mod_LeafPVS (leaf, sv.worldmodel); + + pvsbytes = (sv.worldmodel->numleafs+7)>>3; + if (checkpvs == NULL || pvsbytes > checkpvs_capacity) + { + checkpvs_capacity = pvsbytes; + checkpvs = (byte *) realloc (checkpvs, checkpvs_capacity); + if (!checkpvs) + Sys_Error ("PF_newcheckclient: realloc() failed on %d bytes", checkpvs_capacity); + } + memcpy (checkpvs, pvs, pvsbytes); + + return i; +} + +/* +================= +PF_checkclient + +Returns a client (or object that has a client enemy) that would be a +valid target. + +If there are more than one valid options, they are cycled each frame + +If (self.origin + self.viewofs) is not in the PVS of the current target, +it is not returned at all. + +name checkclient () +================= +*/ +#define MAX_CHECK 16 +static int c_invis, c_notvis; +static void PF_checkclient (void) +{ + edict_t *ent, *self; + mleaf_t *leaf; + int l; + vec3_t view; + +// find a new check if on a new frame + if (sv.time - sv.lastchecktime >= 0.1) + { + sv.lastcheck = PF_newcheckclient (sv.lastcheck); + sv.lastchecktime = sv.time; + } + +// return check if it might be visible + ent = EDICT_NUM(sv.lastcheck); + if (ent->free || ent->v.health <= 0) + { + RETURN_EDICT(sv.edicts); + return; + } + +// if current entity can't possibly see the check entity, return 0 + self = PROG_TO_EDICT(pr_global_struct->self); + VectorAdd (self->v.origin, self->v.view_ofs, view); + leaf = Mod_PointInLeaf (view, sv.worldmodel); + l = (leaf - sv.worldmodel->leafs) - 1; + if ( (l < 0) || !(checkpvs[l>>3] & (1 << (l & 7))) ) + { + c_notvis++; + RETURN_EDICT(sv.edicts); + return; + } + +// might be able to see it + c_invis++; + RETURN_EDICT(ent); +} + +//============================================================================ + + +/* +================= +PF_stuffcmd + +Sends text over to the client's execution buffer + +stuffcmd (clientent, value) +================= +*/ +static void PF_stuffcmd (void) +{ + int entnum; + const char *str; + client_t *old; + + entnum = G_EDICTNUM(OFS_PARM0); + if (entnum < 1 || entnum > svs.maxclients) + PR_RunError ("Parm 0 not a client"); + str = G_STRING(OFS_PARM1); + + old = host_client; + host_client = &svs.clients[entnum-1]; + Host_ClientCommands ("%s", str); + host_client = old; +} + +/* +================= +PF_localcmd + +Sends text over to the client's execution buffer + +localcmd (string) +================= +*/ +static void PF_localcmd (void) +{ + const char *str; + + str = G_STRING(OFS_PARM0); + Cbuf_AddText (str); +} + +/* +================= +PF_cvar + +float cvar (string) +================= +*/ +static void PF_cvar (void) +{ + const char *str; + + str = G_STRING(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = Cvar_VariableValue (str); +} + +/* +================= +PF_cvar_set + +float cvar (string) +================= +*/ +static void PF_cvar_set (void) +{ + const char *var, *val; + + var = G_STRING(OFS_PARM0); + val = G_STRING(OFS_PARM1); + + Cvar_Set (var, val); +} + +/* +================= +PF_findradius + +Returns a chain of entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static void PF_findradius (void) +{ + edict_t *ent, *chain; + float rad; + float *org; + int i; + + chain = (edict_t *)sv.edicts; + + org = G_VECTOR(OFS_PARM0); + rad = G_FLOAT(OFS_PARM1); + rad *= rad; + + ent = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + { + float d, lensq; + if (ent->free) + continue; + if (ent->v.solid == SOLID_NOT) + continue; + + d = org[0] - (ent->v.origin[0] + (ent->v.mins[0] + ent->v.maxs[0]) * 0.5); + lensq = d * d; + if (lensq > rad) + continue; + d = org[1] - (ent->v.origin[1] + (ent->v.mins[1] + ent->v.maxs[1]) * 0.5); + lensq += d * d; + if (lensq > rad) + continue; + d = org[2] - (ent->v.origin[2] + (ent->v.mins[2] + ent->v.maxs[2]) * 0.5); + lensq += d * d; + if (lensq > rad) + continue; + + ent->v.chain = EDICT_TO_PROG(chain); + chain = ent; + } + + RETURN_EDICT(chain); +} + +/* +========= +PF_dprint +========= +*/ +static void PF_dprint (void) +{ + Con_DPrintf ("%s",PF_VarString(0)); +} + +static void PF_ftos (void) +{ + float v; + char *s; + + v = G_FLOAT(OFS_PARM0); + s = PR_GetTempString(); + if (v == (int)v) + sprintf (s, "%d",(int)v); + else + sprintf (s, "%5.1f",v); + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} + +static void PF_fabs (void) +{ + float v; + v = G_FLOAT(OFS_PARM0); + G_FLOAT(OFS_RETURN) = fabs(v); +} + +static void PF_vtos (void) +{ + char *s; + + s = PR_GetTempString(); + sprintf (s, "'%5.1f %5.1f %5.1f'", G_VECTOR(OFS_PARM0)[0], G_VECTOR(OFS_PARM0)[1], G_VECTOR(OFS_PARM0)[2]); + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} + +static void PF_Spawn (void) +{ + edict_t *ed; + + ed = ED_Alloc(); + + RETURN_EDICT(ed); +} + +static void PF_Remove (void) +{ + edict_t *ed; + + ed = G_EDICT(OFS_PARM0); + ED_Free (ed); +} + + +// entity (entity start, .string field, string match) find = #5; +static void PF_Find (void) +{ + int e; + int f; + const char *s, *t; + edict_t *ed; + + e = G_EDICTNUM(OFS_PARM0); + f = G_INT(OFS_PARM1); + s = G_STRING(OFS_PARM2); + if (!s) + PR_RunError ("PF_Find: bad search string"); + + for (e++ ; e < sv.num_edicts ; e++) + { + ed = EDICT_NUM(e); + if (ed->free) + continue; + t = E_STRING(ed,f); + if (!t) + continue; + if (!strcmp(t,s)) + { + RETURN_EDICT(ed); + return; + } + } + + RETURN_EDICT(sv.edicts); +} + +static void PR_CheckEmptyString (const char *s) +{ + if (s[0] <= ' ') + PR_RunError ("Bad string"); +} + +static void PF_precache_file (void) +{ // precache_file is only used to copy files with qcc, it does nothing + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); +} + +static void PF_precache_sound (void) +{ + const char *s; + int i; + + if (sv.state != ss_loading) + PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); + + s = G_STRING(OFS_PARM0); + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + PR_CheckEmptyString (s); + + for (i = 0; i < MAX_SOUNDS; i++) + { + if (!sv.sound_precache[i]) + { + sv.sound_precache[i] = s; + return; + } + if (!strcmp(sv.sound_precache[i], s)) + return; + } + PR_RunError ("PF_precache_sound: overflow"); +} + +static void PF_precache_model (void) +{ + const char *s; + int i; + + if (sv.state != ss_loading) + PR_RunError ("PF_Precache_*: Precache can only be done in spawn functions"); + + s = G_STRING(OFS_PARM0); + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + PR_CheckEmptyString (s); + + for (i = 0; i < MAX_MODELS; i++) + { + if (!sv.model_precache[i]) + { + sv.model_precache[i] = s; + sv.models[i] = Mod_ForName (s, true); + return; + } + if (!strcmp(sv.model_precache[i], s)) + return; + } + PR_RunError ("PF_precache_model: overflow"); +} + + +static void PF_coredump (void) +{ + ED_PrintEdicts (); +} + +static void PF_traceon (void) +{ + pr_trace = true; +} + +static void PF_traceoff (void) +{ + pr_trace = false; +} + +static void PF_eprint (void) +{ + ED_PrintNum (G_EDICTNUM(OFS_PARM0)); +} + +/* +=============== +PF_walkmove + +float(float yaw, float dist) walkmove +=============== +*/ +static void PF_walkmove (void) +{ + edict_t *ent; + float yaw, dist; + vec3_t move; + dfunction_t *oldf; + int oldself; + + ent = PROG_TO_EDICT(pr_global_struct->self); + yaw = G_FLOAT(OFS_PARM0); + dist = G_FLOAT(OFS_PARM1); + + if ( !( (int)ent->v.flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + { + G_FLOAT(OFS_RETURN) = 0; + return; + } + + yaw = yaw * M_PI * 2 / 360; + + move[0] = cos(yaw) * dist; + move[1] = sin(yaw) * dist; + move[2] = 0; + +// save program state, because SV_movestep may call other progs + oldf = pr_xfunction; + oldself = pr_global_struct->self; + + G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true); + + +// restore program state + pr_xfunction = oldf; + pr_global_struct->self = oldself; +} + +/* +=============== +PF_droptofloor + +void() droptofloor +=============== +*/ +static void PF_droptofloor (void) +{ + edict_t *ent; + vec3_t end; + trace_t trace; + + ent = PROG_TO_EDICT(pr_global_struct->self); + + VectorCopy (ent->v.origin, end); + end[2] -= 256; + + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, false, ent); + + if (trace.fraction == 1 || trace.allsolid) + G_FLOAT(OFS_RETURN) = 0; + else + { + VectorCopy (trace.endpos, ent->v.origin); + SV_LinkEdict (ent, false); + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + G_FLOAT(OFS_RETURN) = 1; + } +} + +/* +=============== +PF_lightstyle + +void(float style, string value) lightstyle +=============== +*/ +static void PF_lightstyle (void) +{ + int style; + const char *val; + client_t *client; + int j; + + style = G_FLOAT(OFS_PARM0); + val = G_STRING(OFS_PARM1); + +// bounds check to avoid clobbering sv struct + if (style < 0 || style >= MAX_LIGHTSTYLES) + { + Con_DWarning("PF_lightstyle: invalid style %d\n", style); + return; + } + +// change the string in sv + sv.lightstyles[style] = val; + +// send message to all clients on this server + if (sv.state != ss_active) + return; + + for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) + { + if (client->active || client->spawned) + { + MSG_WriteChar (&client->message, svc_lightstyle); + MSG_WriteChar (&client->message, style); + MSG_WriteString (&client->message, val); + } + } +} + +static void PF_rint (void) +{ + float f; + f = G_FLOAT(OFS_PARM0); + if (f > 0) + G_FLOAT(OFS_RETURN) = (int)(f + 0.5); + else + G_FLOAT(OFS_RETURN) = (int)(f - 0.5); +} + +static void PF_floor (void) +{ + G_FLOAT(OFS_RETURN) = floor(G_FLOAT(OFS_PARM0)); +} + +static void PF_ceil (void) +{ + G_FLOAT(OFS_RETURN) = ceil(G_FLOAT(OFS_PARM0)); +} + + +/* +============= +PF_checkbottom +============= +*/ +static void PF_checkbottom (void) +{ + edict_t *ent; + + ent = G_EDICT(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = SV_CheckBottom (ent); +} + +/* +============= +PF_pointcontents +============= +*/ +static void PF_pointcontents (void) +{ + float *v; + + v = G_VECTOR(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = SV_PointContents (v); +} + +/* +============= +PF_nextent + +entity nextent(entity) +============= +*/ +static void PF_nextent (void) +{ + int i; + edict_t *ent; + + i = G_EDICTNUM(OFS_PARM0); + while (1) + { + i++; + if (i == sv.num_edicts) + { + RETURN_EDICT(sv.edicts); + return; + } + ent = EDICT_NUM(i); + if (!ent->free) + { + RETURN_EDICT(ent); + return; + } + } +} + +/* +============= +PF_aim + +Pick a vector for the player to shoot along +vector aim(entity, missilespeed) +============= +*/ +cvar_t sv_aim = {"sv_aim", "1", CVAR_NONE}; // ericw -- turn autoaim off by default. was 0.93 +static void PF_aim (void) +{ + edict_t *ent, *check, *bestent; + vec3_t start, dir, end, bestdir; + int i, j; + trace_t tr; + float dist, bestdist; + float speed; + + ent = G_EDICT(OFS_PARM0); + speed = G_FLOAT(OFS_PARM1); + (void) speed; /* variable set but not used */ + + VectorCopy (ent->v.origin, start); + start[2] += 20; + +// try sending a trace straight + VectorCopy (pr_global_struct->v_forward, dir); + VectorMA (start, 2048, dir, end); + tr = SV_Move (start, vec3_origin, vec3_origin, end, false, ent); + if (tr.ent && tr.ent->v.takedamage == DAMAGE_AIM + && (!teamplay.value || ent->v.team <= 0 || ent->v.team != tr.ent->v.team) ) + { + VectorCopy (pr_global_struct->v_forward, G_VECTOR(OFS_RETURN)); + return; + } + +// try all possible entities + VectorCopy (dir, bestdir); + bestdist = sv_aim.value; + bestent = NULL; + + check = NEXT_EDICT(sv.edicts); + for (i = 1; i < sv.num_edicts; i++, check = NEXT_EDICT(check) ) + { + if (check->v.takedamage != DAMAGE_AIM) + continue; + if (check == ent) + continue; + if (teamplay.value && ent->v.team > 0 && ent->v.team == check->v.team) + continue; // don't aim at teammate + for (j = 0; j < 3; j++) + end[j] = check->v.origin[j] + 0.5 * (check->v.mins[j] + check->v.maxs[j]); + VectorSubtract (end, start, dir); + VectorNormalize (dir); + dist = DotProduct (dir, pr_global_struct->v_forward); + if (dist < bestdist) + continue; // to far to turn + tr = SV_Move (start, vec3_origin, vec3_origin, end, false, ent); + if (tr.ent == check) + { // can shoot at this one + bestdist = dist; + bestent = check; + } + } + + if (bestent) + { + VectorSubtract (bestent->v.origin, ent->v.origin, dir); + dist = DotProduct (dir, pr_global_struct->v_forward); + VectorScale (pr_global_struct->v_forward, dist, end); + end[2] = dir[2]; + VectorNormalize (end); + VectorCopy (end, G_VECTOR(OFS_RETURN)); + } + else + { + VectorCopy (bestdir, G_VECTOR(OFS_RETURN)); + } +} + +/* +============== +PF_changeyaw + +This was a major timewaster in progs, so it was converted to C +============== +*/ +void PF_changeyaw (void) +{ + edict_t *ent; + float ideal, current, move, speed; + + ent = PROG_TO_EDICT(pr_global_struct->self); + current = anglemod( ent->v.angles[1] ); + ideal = ent->v.ideal_yaw; + speed = ent->v.yaw_speed; + + if (current == ideal) + return; + move = ideal - current; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->v.angles[1] = anglemod (current + move); +} + +/* +=============================================================================== + +MESSAGE WRITING + +=============================================================================== +*/ + +static sizebuf_t *WriteDest (void) +{ + int entnum; + int dest; + edict_t *ent; + + dest = G_FLOAT(OFS_PARM0); + switch (dest) + { + case MSG_BROADCAST: + return &sv.datagram; + + case MSG_ONE: + ent = PROG_TO_EDICT(pr_global_struct->msg_entity); + entnum = NUM_FOR_EDICT(ent); + if (entnum < 1 || entnum > svs.maxclients) + PR_RunError ("WriteDest: not a client"); + return &svs.clients[entnum-1].message; + + case MSG_ALL: + return &sv.reliable_datagram; + + case MSG_INIT: + return sv.signon; + + default: + PR_RunError ("WriteDest: bad destination"); + break; + } + + return NULL; +} + +static void PF_WriteByte (void) +{ + MSG_WriteByte (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteChar (void) +{ + MSG_WriteChar (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteShort (void) +{ + MSG_WriteShort (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteLong (void) +{ + MSG_WriteLong (WriteDest(), G_FLOAT(OFS_PARM1)); +} + +static void PF_WriteAngle (void) +{ + MSG_WriteAngle (WriteDest(), G_FLOAT(OFS_PARM1), sv.protocolflags); +} + +static void PF_WriteCoord (void) +{ + MSG_WriteCoord (WriteDest(), G_FLOAT(OFS_PARM1), sv.protocolflags); +} + +static void PF_WriteString (void) +{ + MSG_WriteString (WriteDest(), LOC_GetString(G_STRING(OFS_PARM1))); +} + +static void PF_WriteEntity (void) +{ + MSG_WriteShort (WriteDest(), G_EDICTNUM(OFS_PARM1)); +} + +//============================================================================= + +static void PF_makestatic (void) +{ + edict_t *ent; + int i; + int bits = 0; //johnfitz -- PROTOCOL_FITZQUAKE + + ent = G_EDICT(OFS_PARM0); + + //johnfitz -- don't send invisible static entities + if (ent->alpha == ENTALPHA_ZERO) { + ED_Free (ent); + return; + } + //johnfitz + + //johnfitz -- PROTOCOL_FITZQUAKE + if (sv.protocol == PROTOCOL_NETQUAKE) + { + if (SV_ModelIndex(PR_GetString(ent->v.model)) & 0xFF00 || (int)(ent->v.frame) & 0xFF00) + { + ED_Free (ent); + return; //can't display the correct model & frame, so don't show it at all + } + } + else + { + if (SV_ModelIndex(PR_GetString(ent->v.model)) & 0xFF00) + bits |= B_LARGEMODEL; + if ((int)(ent->v.frame) & 0xFF00) + bits |= B_LARGEFRAME; + if (ent->alpha != ENTALPHA_DEFAULT) + bits |= B_ALPHA; + + if (sv.protocol == PROTOCOL_RMQ) + { + eval_t* val; + val = GetEdictFieldValue(ent, "scale"); + if (val) + ent->scale = ENTSCALE_ENCODE(val->_float); + else + ent->scale = ENTSCALE_DEFAULT; + + if (ent->scale != ENTSCALE_DEFAULT) + bits |= B_SCALE; + } + } + + SV_ReserveSignonSpace (34); + + if (bits) + { + MSG_WriteByte (sv.signon, svc_spawnstatic2); + MSG_WriteByte (sv.signon, bits); + } + else + MSG_WriteByte (sv.signon, svc_spawnstatic); + + if (bits & B_LARGEMODEL) + MSG_WriteShort (sv.signon, SV_ModelIndex(PR_GetString(ent->v.model))); + else + MSG_WriteByte (sv.signon, SV_ModelIndex(PR_GetString(ent->v.model))); + + if (bits & B_LARGEFRAME) + MSG_WriteShort (sv.signon, ent->v.frame); + else + MSG_WriteByte (sv.signon, ent->v.frame); + //johnfitz + + MSG_WriteByte (sv.signon, ent->v.colormap); + MSG_WriteByte (sv.signon, ent->v.skin); + for (i = 0; i < 3; i++) + { + MSG_WriteCoord(sv.signon, ent->v.origin[i], sv.protocolflags); + MSG_WriteAngle(sv.signon, ent->v.angles[i], sv.protocolflags); + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & B_ALPHA) + MSG_WriteByte (sv.signon, ent->alpha); + //johnfitz + + if (bits & B_SCALE) + MSG_WriteByte (sv.signon, ent->scale); + +// throw the entity away now + ED_Free (ent); +} + +//============================================================================= + +/* +============== +PF_setspawnparms +============== +*/ +static void PF_setspawnparms (void) +{ + edict_t *ent; + int i; + client_t *client; + + ent = G_EDICT(OFS_PARM0); + i = NUM_FOR_EDICT(ent); + if (i < 1 || i > svs.maxclients) + PR_RunError ("Entity is not a client"); + + // copy spawn parms out of the client_t + client = svs.clients + (i-1); + + for (i = 0; i < NUM_SPAWN_PARMS; i++) + (&pr_global_struct->parm1)[i] = client->spawn_parms[i]; +} + +/* +============== +PF_changelevel +============== +*/ +static void PF_changelevel (void) +{ + const char *s; + +// make sure we don't issue two changelevels + if (svs.changelevel_issued) + return; + svs.changelevel_issued = true; + + s = G_STRING(OFS_PARM0); + Cbuf_AddText (va("changelevel %s\n",s)); +} + +/* +============== +2021 re-release +============== +*/ +static void PF_finalefinished (void) +{ + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_CheckPlayerEXFlags (void) +{ + G_FLOAT(OFS_RETURN) = 0; +} +static void PF_walkpathtogoal (void) +{ + G_FLOAT(OFS_RETURN) = 0; /* PATH_ERROR */ +} +static void PF_localsound (void) +{ + const char *sample; + int entnum; + + entnum = G_EDICTNUM(OFS_PARM0); + sample = G_STRING(OFS_PARM1); + if (entnum < 1 || entnum > svs.maxclients) { + Con_Printf ("tried to localsound to a non-client\n"); + return; + } + SV_LocalSound (&svs.clients[entnum-1], sample); +} + +static void PF_Fixme (void) +{ + PR_RunError ("unimplemented builtin"); +} + + +static builtin_t pr_builtin[] = +{ + PF_Fixme, + PF_makevectors, // void(entity e) makevectors = #1 + PF_setorigin, // void(entity e, vector o) setorigin = #2 + PF_setmodel, // void(entity e, string m) setmodel = #3 + PF_setsize, // void(entity e, vector min, vector max) setsize = #4 + PF_Fixme, // void(entity e, vector min, vector max) setabssize = #5 + PF_break, // void() break = #6 + PF_random, // float() random = #7 + PF_sound, // void(entity e, float chan, string samp) sound = #8 + PF_normalize, // vector(vector v) normalize = #9 + PF_error, // void(string e) error = #10 + PF_objerror, // void(string e) objerror = #11 + PF_vlen, // float(vector v) vlen = #12 + PF_vectoyaw, // float(vector v) vectoyaw = #13 + PF_Spawn, // entity() spawn = #14 + PF_Remove, // void(entity e) remove = #15 + PF_traceline, // float(vector v1, vector v2, float tryents) traceline = #16 + PF_checkclient, // entity() clientlist = #17 + PF_Find, // entity(entity start, .string fld, string match) find = #18 + PF_precache_sound, // void(string s) precache_sound = #19 + PF_precache_model, // void(string s) precache_model = #20 + PF_stuffcmd, // void(entity client, string s)stuffcmd = #21 + PF_findradius, // entity(vector org, float rad) findradius = #22 + PF_bprint, // void(string s) bprint = #23 + PF_sprint, // void(entity client, string s) sprint = #24 + PF_dprint, // void(string s) dprint = #25 + PF_ftos, // void(string s) ftos = #26 + PF_vtos, // void(string s) vtos = #27 + PF_coredump, + PF_traceon, + PF_traceoff, + PF_eprint, // void(entity e) debug print an entire entity + PF_walkmove, // float(float yaw, float dist) walkmove + PF_Fixme, // float(float yaw, float dist) walkmove + PF_droptofloor, + PF_lightstyle, + PF_rint, + PF_floor, + PF_ceil, + PF_Fixme, + PF_checkbottom, + PF_pointcontents, + PF_Fixme, + PF_fabs, + PF_aim, + PF_cvar, + PF_localcmd, + PF_nextent, + PF_particle, + PF_changeyaw, + PF_Fixme, + PF_vectoangles, + + PF_WriteByte, + PF_WriteChar, + PF_WriteShort, + PF_WriteLong, + PF_WriteCoord, + PF_WriteAngle, + PF_WriteString, + PF_WriteEntity, + + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + + SV_MoveToGoal, + PF_precache_file, + PF_makestatic, + + PF_changelevel, + PF_Fixme, + + PF_cvar_set, + PF_centerprint, + + PF_ambientsound, + + PF_precache_model, + PF_precache_sound, // precache_sound2 is different only for qcc + PF_precache_file, + + PF_setspawnparms, + + // 2021 re-release + PF_finalefinished, // float() finaleFinished = #79 + PF_localsound, // void localsound (entity client, string sample) = #80 + PF_Fixme, // void draw_point (vector point, float colormap, float lifetime, float depthtest) = #81 + PF_Fixme, // void draw_line (vector start, vector end, float colormap, float lifetime, float depthtest) = #82 + PF_Fixme, // void draw_arrow (vector start, vector end, float colormap, float size, float lifetime, float depthtest) = #83 + PF_Fixme, // void draw_ray (vector start, vector direction, float length, float colormap, float size, float lifetime, float depthtest) = #84 + PF_Fixme, // void draw_circle (vector origin, float radius, float colormap, float lifetime, float depthtest) = #85 + PF_Fixme, // void draw_bounds (vector min, vector max, float colormap, float lifetime, float depthtest) = #86 + PF_Fixme, // void draw_worldtext (string s, vector origin, float size, float lifetime, float depthtest) = #87 + PF_Fixme, // void draw_sphere (vector origin, float radius, float colormap, float lifetime, float depthtest) = #88 + PF_Fixme, // void draw_cylinder (vector origin, float halfHeight, float radius, float colormap, float lifetime, float depthtest) = #89 + + PF_CheckPlayerEXFlags, + PF_walkpathtogoal, + + PF_Fixme, +}; + +const builtin_t *pr_builtins = pr_builtin; +const int pr_numbuiltins = Q_COUNTOF(pr_builtin); diff --git a/Quake/pr_comp.hpp b/Quake/pr_comp.hpp new file mode 100644 index 0000000..bc6c62e --- /dev/null +++ b/Quake/pr_comp.hpp @@ -0,0 +1,197 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __PR_COMP_H +#define __PR_COMP_H + +// this file is shared by quake and qcc + +typedef int func_t; +typedef int string_t; + +typedef enum +{ + ev_bad = -1, + ev_void = 0, + ev_string, + ev_float, + ev_vector, + ev_entity, + ev_field, + ev_function, + ev_pointer +} etype_t; + +#define OFS_NULL 0 +#define OFS_RETURN 1 +#define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors +#define OFS_PARM1 7 +#define OFS_PARM2 10 +#define OFS_PARM3 13 +#define OFS_PARM4 16 +#define OFS_PARM5 19 +#define OFS_PARM6 22 +#define OFS_PARM7 25 +#define RESERVED_OFS 28 + + +enum +{ + OP_DONE, + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_ADD_F, + OP_ADD_V, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_FNC, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_FNC, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_LOAD_F, + OP_LOAD_V, + OP_LOAD_S, + OP_LOAD_ENT, + OP_LOAD_FLD, + OP_LOAD_FNC, + + OP_ADDRESS, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_FLD, + OP_STORE_FNC, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, + OP_STOREP_FLD, + OP_STOREP_FNC, + + OP_RETURN, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + OP_NOT_FNC, + OP_IF, + OP_IFNOT, + OP_CALL0, + OP_CALL1, + OP_CALL2, + OP_CALL3, + OP_CALL4, + OP_CALL5, + OP_CALL6, + OP_CALL7, + OP_CALL8, + OP_STATE, + OP_GOTO, + OP_AND, + OP_OR, + + OP_BITAND, + OP_BITOR +}; + +typedef struct statement_s +{ + unsigned short op; + short a, b, c; +} dstatement_t; + +typedef struct +{ + unsigned short type; // if DEF_SAVEGLOBAL bit is set + // the variable needs to be saved in savegames + unsigned short ofs; + int s_name; +} ddef_t; + +#define DEF_SAVEGLOBAL (1<<15) + +#define MAX_PARMS 8 + +typedef struct +{ + int first_statement; // negative numbers are builtins + int parm_start; + int locals; // total ints of parms + locals + + int profile; // runtime + + int s_name; + int s_file; // source file defined in + + int numparms; + byte parm_size[MAX_PARMS]; +} dfunction_t; + + +#define PROG_VERSION 6 +typedef struct +{ + int version; + int crc; // check of header file + + int ofs_statements; + int numstatements; // statement 0 is an error + + int ofs_globaldefs; + int numglobaldefs; + + int ofs_fielddefs; + int numfielddefs; + + int ofs_functions; + int numfunctions; // function 0 is an empty + + int ofs_strings; + int numstrings; // first string is a null string + + int ofs_globals; + int numglobals; + + int entityfields; +} dprograms_t; + +#endif /* __PR_COMP_H */ + diff --git a/Quake/pr_edict.cpp b/Quake/pr_edict.cpp new file mode 100644 index 0000000..452d09f --- /dev/null +++ b/Quake/pr_edict.cpp @@ -0,0 +1,1401 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_edict.c -- entity dictionary + +#include "quakedef.hpp" + +dprograms_t *progs; +dfunction_t *pr_functions; + +static char *pr_strings; +static int pr_stringssize; +static const char **pr_knownstrings; +static int pr_maxknownstrings; +static int pr_numknownstrings; +static ddef_t *pr_fielddefs; +static ddef_t *pr_globaldefs; + +qboolean pr_alpha_supported; //johnfitz +int pr_effects_mask; // only enable 2021 rerelease quad/penta dlights when applicable + +dstatement_t *pr_statements; +globalvars_t *pr_global_struct; +float *pr_globals; // same as pr_global_struct +int pr_edict_size; // in bytes + +unsigned short pr_crc; + +const int type_size[NUM_TYPE_SIZES] = { + 1, // ev_void + 1, // sizeof(string_t) / 4 // ev_string + 1, // ev_float + 3, // ev_vector + 1, // ev_entity + 1, // ev_field + 1, // sizeof(func_t) / 4 // ev_function + 1 // sizeof(void *) / 4 // ev_pointer +}; + +static ddef_t *ED_FieldAtOfs (int ofs); +static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s); + +#define MAX_FIELD_LEN 64 +#define GEFV_CACHESIZE 2 + +typedef struct { + ddef_t *pcache; + char field[MAX_FIELD_LEN]; +} gefv_cache; + +static gefv_cache gefvCache[GEFV_CACHESIZE] = +{ + { NULL, "" }, + { NULL, "" } +}; + +cvar_t nomonsters = {"nomonsters", "0", CVAR_NONE}; +cvar_t gamecfg = {"gamecfg", "0", CVAR_NONE}; +cvar_t scratch1 = {"scratch1", "0", CVAR_NONE}; +cvar_t scratch2 = {"scratch2", "0", CVAR_NONE}; +cvar_t scratch3 = {"scratch3", "0", CVAR_NONE}; +cvar_t scratch4 = {"scratch4", "0", CVAR_NONE}; +cvar_t savedgamecfg = {"savedgamecfg", "0", CVAR_ARCHIVE}; +cvar_t saved1 = {"saved1", "0", CVAR_ARCHIVE}; +cvar_t saved2 = {"saved2", "0", CVAR_ARCHIVE}; +cvar_t saved3 = {"saved3", "0", CVAR_ARCHIVE}; +cvar_t saved4 = {"saved4", "0", CVAR_ARCHIVE}; + +/* +================= +ED_ClearEdict + +Sets everything to NULL +================= +*/ +void ED_ClearEdict (edict_t *e) +{ + memset (&e->v, 0, progs->entityfields * 4); + e->free = false; +} + +/* +================= +ED_Alloc + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *ED_Alloc (void) +{ + int i; + edict_t *e; + + for (i = svs.maxclients + 1; i < sv.num_edicts; i++) + { + e = EDICT_NUM(i); + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if (e->free && ( e->freetime < 2 || sv.time - e->freetime > 0.5 ) ) + { + ED_ClearEdict (e); + return e; + } + } + + if (i == sv.max_edicts) //johnfitz -- use sv.max_edicts instead of MAX_EDICTS + Host_Error ("ED_Alloc: no free edicts (max_edicts is %i)", sv.max_edicts); + + sv.num_edicts++; + e = EDICT_NUM(i); + memset(e, 0, pr_edict_size); // ericw -- switched sv.edicts to malloc(), so we are accessing uninitialized memory and must fully zero it, not just ED_ClearEdict + e->baseline.scale = ENTSCALE_DEFAULT; + + return e; +} + +/* +================= +ED_Free + +Marks the edict as free +FIXME: walk all entities and NULL out references to this entity +================= +*/ +void ED_Free (edict_t *ed) +{ + SV_UnlinkEdict (ed); // unlink from world bsp + + ed->free = true; + ed->v.model = 0; + ed->v.takedamage = 0; + ed->v.modelindex = 0; + ed->v.colormap = 0; + ed->v.skin = 0; + ed->v.frame = 0; + VectorCopy (vec3_origin, ed->v.origin); + VectorCopy (vec3_origin, ed->v.angles); + ed->v.nextthink = -1; + ed->v.solid = 0; + ed->alpha = ENTALPHA_DEFAULT; //johnfitz -- reset alpha for next entity + ed->scale = ENTSCALE_DEFAULT; + + ed->freetime = sv.time; +} + +//=========================================================================== + +/* +============ +ED_GlobalAtOfs +============ +*/ +static ddef_t *ED_GlobalAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numglobaldefs; i++) + { + def = &pr_globaldefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +ED_FieldAtOfs +============ +*/ +static ddef_t *ED_FieldAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numfielddefs; i++) + { + def = &pr_fielddefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +ED_FindField +============ +*/ +static ddef_t *ED_FindField (const char *name) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numfielddefs; i++) + { + def = &pr_fielddefs[i]; + if ( !strcmp(PR_GetString(def->s_name), name) ) + return def; + } + return NULL; +} + + +/* +============ +ED_FindGlobal +============ +*/ +static ddef_t *ED_FindGlobal (const char *name) +{ + ddef_t *def; + int i; + + for (i = 0; i < progs->numglobaldefs; i++) + { + def = &pr_globaldefs[i]; + if ( !strcmp(PR_GetString(def->s_name), name) ) + return def; + } + return NULL; +} + + +/* +============ +ED_FindFunction +============ +*/ +static dfunction_t *ED_FindFunction (const char *fn_name) +{ + dfunction_t *func; + int i; + + for (i = 0; i < progs->numfunctions; i++) + { + func = &pr_functions[i]; + if ( !strcmp(PR_GetString(func->s_name), fn_name) ) + return func; + } + return NULL; +} + +/* +============ +GetEdictFieldValue +============ +*/ +eval_t *GetEdictFieldValue(edict_t *ed, const char *field) +{ + ddef_t *def = NULL; + int i; + static int rep = 0; + + for (i = 0; i < GEFV_CACHESIZE; i++) + { + if (!strcmp(field, gefvCache[i].field)) + { + def = gefvCache[i].pcache; + goto Done; + } + } + + def = ED_FindField (field); + + if (strlen(field) < MAX_FIELD_LEN) + { + gefvCache[rep].pcache = def; + strcpy (gefvCache[rep].field, field); + rep ^= 1; + } + +Done: + if (!def) + return NULL; + + return (eval_t *)((char *)&ed->v + def->ofs*4); +} + + +/* +============ +PR_ValueString +(etype_t type, eval_t *val) + +Returns a string describing *data in a type specific manner +============= +*/ +static const char *PR_ValueString (int type, eval_t *val) +{ + static char line[512]; + ddef_t *def; + dfunction_t *f; + + type &= ~DEF_SAVEGLOBAL; + + switch (type) + { + case ev_string: + q_snprintf (line, sizeof(line), "%s", PR_GetString(val->string)); + break; + case ev_entity: + q_snprintf (line, sizeof(line), "entity %i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)) ); + break; + case ev_function: + f = pr_functions + val->function; + q_snprintf (line, sizeof(line), "%s()", PR_GetString(f->s_name)); + break; + case ev_field: + def = ED_FieldAtOfs ( val->_int ); + q_snprintf (line, sizeof(line), ".%s", PR_GetString(def->s_name)); + break; + case ev_void: + q_snprintf (line, sizeof(line), "void"); + break; + case ev_float: + q_snprintf (line, sizeof(line), "%5.1f", val->_float); + break; + case ev_vector: + q_snprintf (line, sizeof(line), "'%5.1f %5.1f %5.1f'", val->vector[0], val->vector[1], val->vector[2]); + break; + case ev_pointer: + q_snprintf (line, sizeof(line), "pointer"); + break; + default: + q_snprintf (line, sizeof(line), "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PR_UglyValueString +(etype_t type, eval_t *val) + +Returns a string describing *data in a type specific manner +Easier to parse than PR_ValueString +============= +*/ +static const char *PR_UglyValueString (int type, eval_t *val) +{ + static char line[1024]; + ddef_t *def; + dfunction_t *f; + + type &= ~DEF_SAVEGLOBAL; + + switch (type) + { + case ev_string: + q_snprintf (line, sizeof(line), "%s", PR_GetString(val->string)); + break; + case ev_entity: + q_snprintf (line, sizeof(line), "%i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict))); + break; + case ev_function: + f = pr_functions + val->function; + q_snprintf (line, sizeof(line), "%s", PR_GetString(f->s_name)); + break; + case ev_field: + def = ED_FieldAtOfs ( val->_int ); + q_snprintf (line, sizeof(line), "%s", PR_GetString(def->s_name)); + break; + case ev_void: + q_snprintf (line, sizeof(line), "void"); + break; + case ev_float: + q_snprintf (line, sizeof(line), "%f", val->_float); + break; + case ev_vector: + q_snprintf (line, sizeof(line), "%f %f %f", val->vector[0], val->vector[1], val->vector[2]); + break; + default: + q_snprintf (line, sizeof(line), "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PR_GlobalString + +Returns a string with a description and the contents of a global, +padded to 20 field width +============ +*/ +const char *PR_GlobalString (int ofs) +{ + static char line[512]; + static const int lastchari = Q_COUNTOF(line) - 2; + const char *s; + int i; + ddef_t *def; + void *val; + + val = (void *)&pr_globals[ofs]; + def = ED_GlobalAtOfs(ofs); + if (!def) + q_snprintf (line, sizeof(line), "%i(?)", ofs); + else + { + s = PR_ValueString (def->type, (eval_t *)val); + q_snprintf (line, sizeof(line), "%i(%s)%s", ofs, PR_GetString(def->s_name), s); + } + + i = strlen(line); + for ( ; i < 20; i++) + strcat (line, " "); + + if (i < lastchari) + strcat (line, " "); + else + line[lastchari] = ' '; + + return line; +} + +const char *PR_GlobalStringNoContents (int ofs) +{ + static char line[512]; + static const int lastchari = Q_COUNTOF(line) - 2; + int i; + ddef_t *def; + + def = ED_GlobalAtOfs(ofs); + if (!def) + q_snprintf (line, sizeof(line), "%i(?)", ofs); + else + q_snprintf (line, sizeof(line), "%i(%s)", ofs, PR_GetString(def->s_name)); + + i = strlen(line); + for ( ; i < 20; i++) + strcat (line, " "); + + if (i < lastchari) + strcat (line, " "); + else + line[lastchari] = ' '; + + return line; +} + + +/* +============= +ED_Print + +For debugging +============= +*/ +void ED_Print (edict_t *ed) +{ + ddef_t *d; + int *v; + int i, j, l; + const char *name; + int type; + + if (ed->free) + { + Con_Printf ("FREE\n"); + return; + } + + Con_SafePrintf("\nEDICT %i:\n", NUM_FOR_EDICT(ed)); //johnfitz -- was Con_Printf + for (i = 1; i < progs->numfielddefs; i++) + { + d = &pr_fielddefs[i]; + name = PR_GetString(d->s_name); + l = strlen (name); + if (l > 1 && name[l - 2] == '_') + continue; // skip _x, _y, _z vars + + v = (int *)((char *)&ed->v + d->ofs*4); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + + if (type >= NUM_TYPE_SIZES) + continue; + + for (j = 0; j < type_size[type]; j++) + { + if (v[j]) + break; + } + if (j == type_size[type]) + continue; + + Con_SafePrintf ("%s", name); //johnfitz -- was Con_Printf + while (l++ < 15) + Con_SafePrintf (" "); //johnfitz -- was Con_Printf + + Con_SafePrintf ("%s\n", PR_ValueString(d->type, (eval_t *)v)); //johnfitz -- was Con_Printf + } +} + +/* +============= +ED_Write + +For savegames +============= +*/ +void ED_Write (FILE *f, edict_t *ed) +{ + ddef_t *d; + int *v; + int i, j; + const char *name; + int type; + + fprintf (f, "{\n"); + + if (ed->free) + { + fprintf (f, "}\n"); + return; + } + + for (i = 1; i < progs->numfielddefs; i++) + { + d = &pr_fielddefs[i]; + name = PR_GetString(d->s_name); + j = strlen (name); + if (j > 1 && name[j - 2] == '_') + continue; // skip _x, _y, _z vars + + v = (int *)((char *)&ed->v + d->ofs*4); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + + if (type >= NUM_TYPE_SIZES) + continue; + + for (j = 0; j < type_size[type]; j++) + { + if (v[j]) + break; + } + if (j == type_size[type]) + continue; + + fprintf (f, "\"%s\" ", name); + fprintf (f, "\"%s\"\n", PR_UglyValueString(d->type, (eval_t *)v)); + } + + //johnfitz -- save entity alpha manually when progs.dat doesn't know about alpha + if (!pr_alpha_supported && ed->alpha != ENTALPHA_DEFAULT) + fprintf (f, "\"alpha\" \"%f\"\n", ENTALPHA_TOSAVE(ed->alpha)); + //johnfitz + + fprintf (f, "}\n"); +} + +void ED_PrintNum (int ent) +{ + ED_Print (EDICT_NUM(ent)); +} + +/* +============= +ED_PrintEdicts + +For debugging, prints all the entities in the current server +============= +*/ +void ED_PrintEdicts (void) +{ + int i; + + if (!sv.active) + return; + + Con_Printf ("%i entities\n", sv.num_edicts); + for (i = 0; i < sv.num_edicts; i++) + ED_PrintNum (i); +} + +/* +============= +ED_PrintEdict_f + +For debugging, prints a single edicy +============= +*/ +static void ED_PrintEdict_f (void) +{ + int i; + + if (!sv.active) + return; + + i = Q_atoi (Cmd_Argv(1)); + if (i < 0 || i >= sv.num_edicts) + { + Con_Printf("Bad edict number\n"); + return; + } + ED_PrintNum (i); +} + +/* +============= +ED_Count + +For debugging +============= +*/ +static void ED_Count (void) +{ + edict_t *ent; + int i, active, models, solid, step; + + if (!sv.active) + return; + + active = models = solid = step = 0; + for (i = 0; i < sv.num_edicts; i++) + { + ent = EDICT_NUM(i); + if (ent->free) + continue; + active++; + if (ent->v.solid) + solid++; + if (ent->v.model) + models++; + if (ent->v.movetype == MOVETYPE_STEP) + step++; + } + + Con_Printf ("num_edicts:%3i\n", sv.num_edicts); + Con_Printf ("active :%3i\n", active); + Con_Printf ("view :%3i\n", models); + Con_Printf ("touch :%3i\n", solid); + Con_Printf ("step :%3i\n", step); +} + + +/* +============================================================================== + +ARCHIVING GLOBALS + +FIXME: need to tag constants, doesn't really work +============================================================================== +*/ + +/* +============= +ED_WriteGlobals +============= +*/ +void ED_WriteGlobals (FILE *f) +{ + ddef_t *def; + int i; + const char *name; + int type; + + fprintf (f, "{\n"); + for (i = 0; i < progs->numglobaldefs; i++) + { + def = &pr_globaldefs[i]; + type = def->type; + if ( !(def->type & DEF_SAVEGLOBAL) ) + continue; + type &= ~DEF_SAVEGLOBAL; + + if (type != ev_string && type != ev_float && type != ev_entity) + continue; + + name = PR_GetString(def->s_name); + fprintf (f, "\"%s\" ", name); + fprintf (f, "\"%s\"\n", PR_UglyValueString(type, (eval_t *)&pr_globals[def->ofs])); + } + fprintf (f, "}\n"); +} + +/* +============= +ED_ParseGlobals +============= +*/ +const char *ED_ParseGlobals (const char *data) +{ + char keyname[64]; + ddef_t *key; + + while (1) + { + // parse key + data = COM_Parse (data); + if (com_token[0] == '}') + break; + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + q_strlcpy (keyname, com_token, sizeof(keyname)); + + // parse value + data = COM_Parse (data); + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + Host_Error ("ED_ParseEntity: closing brace without data"); + + key = ED_FindGlobal (keyname); + if (!key) + { + Con_Printf ("'%s' is not a global\n", keyname); + continue; + } + + if (!ED_ParseEpair ((void *)pr_globals, key, com_token)) + Host_Error ("ED_ParseGlobals: parse error"); + } + return data; +} + +//============================================================================ + + +/* +============= +ED_NewString +============= +*/ +static string_t ED_NewString (const char *string) +{ + char *new_p; + int i, l; + string_t num; + + l = strlen(string) + 1; + num = PR_AllocString (l, &new_p); + + for (i = 0; i < l; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return num; +} + + +/* +============= +ED_ParseEval + +Can parse either fields or globals +returns false if error +============= +*/ +static qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s) +{ + int i; + char string[128]; + ddef_t *def; + char *v, *w; + char *end; + void *d; + dfunction_t *func; + + d = (void *)((int *)base + key->ofs); + + switch (key->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + *(string_t *)d = ED_NewString(s); + break; + + case ev_float: + *(float *)d = atof (s); + break; + + case ev_vector: + q_strlcpy (string, s, sizeof(string)); + end = (char *)string + strlen(string); + v = string; + w = string; + + for (i = 0; i < 3 && (w <= end); i++) // ericw -- added (w <= end) check + { + // set v to the next space (or 0 byte), and change that char to a 0 byte + while (*v && *v != ' ') + v++; + *v = 0; + ((float *)d)[i] = atof (w); + w = v = v+1; + } + // ericw -- fill remaining elements to 0 in case we hit the end of string + // before reading 3 floats. + if (i < 3) + { + Con_DWarning ("Avoided reading garbage for \"%s\" \"%s\"\n", PR_GetString(key->s_name), s); + for (; i < 3; i++) + ((float *)d)[i] = 0.0f; + } + break; + + case ev_entity: + *(int *)d = EDICT_TO_PROG(EDICT_NUM(atoi (s))); + break; + + case ev_field: + def = ED_FindField (s); + if (!def) + { + //johnfitz -- HACK -- suppress error becuase fog/sky fields might not be mentioned in defs.qc + if (strncmp(s, "sky", 3) && strcmp(s, "fog")) + Con_DPrintf ("Can't find field %s\n", s); + return false; + } + *(int *)d = G_INT(def->ofs); + break; + + case ev_function: + func = ED_FindFunction (s); + if (!func) + { + Con_Printf ("Can't find function %s\n", s); + return false; + } + *(func_t *)d = func - pr_functions; + break; + + default: + break; + } + return true; +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +Used for initial level load and for savegames. +==================== +*/ +const char *ED_ParseEdict (const char *data, edict_t *ent) +{ + ddef_t *key; + char keyname[256]; + qboolean anglehack, init; + int n; + + init = false; + + // clear it + if (ent != sv.edicts) // hack + memset (&ent->v, 0, progs->entityfields * 4); + + // go through all the dictionary pairs + while (1) + { + // parse key + data = COM_Parse (data); + if (com_token[0] == '}') + break; + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + // anglehack is to allow QuakeEd to write single scalar angles + // and allow them to be turned into vectors. (FIXME...) + if (!strcmp(com_token, "angle")) + { + strcpy (com_token, "angles"); + anglehack = true; + } + else + anglehack = false; + + // FIXME: change light to _light to get rid of this hack + if (!strcmp(com_token, "light")) + strcpy (com_token, "light_lev"); // hack for single light def + + q_strlcpy (keyname, com_token, sizeof(keyname)); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + // HACK: we allow truncation when reading the wad field, + // otherwise maps using lots of wads with absolute paths + // could cause a parse error + data = COM_ParseEx (data, !strcmp (keyname, "wad") ? CPE_ALLOWTRUNC : CPE_NOTRUNC); + if (!data) + Host_Error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + Host_Error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + //johnfitz -- hack to support .alpha even when progs.dat doesn't know about it + if (!strcmp(keyname, "alpha")) + ent->alpha = ENTALPHA_ENCODE(Q_atof(com_token)); + //johnfitz + + key = ED_FindField (keyname); + if (!key) + { + //johnfitz -- HACK -- suppress error becuase fog/sky/alpha fields might not be mentioned in defs.qc + if (strncmp(keyname, "sky", 3) && strcmp(keyname, "fog") && strcmp(keyname, "alpha")) + Con_DPrintf ("\"%s\" is not a field\n", keyname); //johnfitz -- was Con_Printf + continue; + } + + if (anglehack) + { + char temp[32]; + strcpy (temp, com_token); + sprintf (com_token, "0 %s 0", temp); + } + + if (!ED_ParseEpair ((void *)&ent->v, key, com_token)) + Host_Error ("ED_ParseEdict: parse error"); + } + + if (!init) + ent->free = true; + + return data; +} + + +/* +================ +ED_LoadFromFile + +The entities are directly placed in the array, rather than allocated with +ED_Alloc, because otherwise an error loading the map would have entity +number references out of order. + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. + +Used for both fresh maps and savegame loads. A fresh map would also need +to call ED_CallSpawnFunctions () to let the objects initialize themselves. +================ +*/ +void ED_LoadFromFile (const char *data) +{ + dfunction_t *func; + edict_t *ent = NULL; + int inhibit = 0; + + pr_global_struct->time = sv.time; + + // parse ents + while (1) + { + // parse the opening brace + data = COM_Parse (data); + if (!data) + break; + if (com_token[0] != '{') + Host_Error ("ED_LoadFromFile: found %s when expecting {",com_token); + + if (!ent) + ent = EDICT_NUM(0); + else + ent = ED_Alloc (); + data = ED_ParseEdict (data, ent); + + // remove things from different skill levels or deathmatch + if (deathmatch.value) + { + if (((int)ent->v.spawnflags & SPAWNFLAG_NOT_DEATHMATCH)) + { + ED_Free (ent); + inhibit++; + continue; + } + } + else if ((current_skill == 0 && ((int)ent->v.spawnflags & SPAWNFLAG_NOT_EASY)) + || (current_skill == 1 && ((int)ent->v.spawnflags & SPAWNFLAG_NOT_MEDIUM)) + || (current_skill >= 2 && ((int)ent->v.spawnflags & SPAWNFLAG_NOT_HARD)) ) + { + ED_Free (ent); + inhibit++; + continue; + } + +// +// immediately call spawn function +// + if (!ent->v.classname) + { + Con_SafePrintf ("No classname for:\n"); //johnfitz -- was Con_Printf + ED_Print (ent); + ED_Free (ent); + continue; + } + + // look for the spawn function + func = ED_FindFunction ( PR_GetString(ent->v.classname) ); + + if (!func) + { + Con_SafePrintf ("No spawn function for:\n"); //johnfitz -- was Con_Printf + ED_Print (ent); + ED_Free (ent); + continue; + } + + SV_ReserveSignonSpace (512); + + pr_global_struct->self = EDICT_TO_PROG(ent); + PR_ExecuteProgram (func - pr_functions); + } + + Con_DPrintf ("%i entities inhibited\n", inhibit); +} + + +/* +=============== +PR_HasGlobal +=============== +*/ +static qboolean PR_HasGlobal (const char *name, float value) +{ + ddef_t *g = ED_FindGlobal (name); + return g && (g->type & ~DEF_SAVEGLOBAL) == ev_float && G_FLOAT (g->ofs) == value; +} + + +/* +=============== +PR_FindSupportedEffects + +Checks for the presence of Quake 2021 release effects flags and returns a mask +with the correspondings bits either on or off depending on the result, in order +to avoid conflicts (e.g. Arcane Dimensions uses bit 32 for its explosions) +=============== +*/ +static int PR_FindSupportedEffects (void) +{ + qboolean isqex = + PR_HasGlobal ("EF_QUADLIGHT", EF_QEX_QUADLIGHT) && + (PR_HasGlobal ("EF_PENTLIGHT", EF_QEX_PENTALIGHT) || PR_HasGlobal ("EF_PENTALIGHT", EF_QEX_PENTALIGHT)) + ; + return isqex ? -1 : -1 & ~(EF_QEX_QUADLIGHT|EF_QEX_PENTALIGHT|EF_QEX_CANDLELIGHT); +} + + +/* +=============== +PR_PatchRereleaseBuiltins + +for 2021 re-release +=============== +*/ +static const exbuiltin_t exbuiltins[] = { + /* Update-1 adds the following builtins with new ids. Patch them to use old indices. + * (https://steamcommunity.com/games/2310/announcements/detail/2943653788150871156) */ + { "centerprint", -90, -73 }, + { "bprint", -91, -23 }, + { "sprint", -92, -24 }, + + /* Update-3 changes its unique builtins to be looked up by name instead of builtin + * numbers, to avoid conflict with other engines. Patch them to use our indices. + * (https://steamcommunity.com/games/2310/announcements/detail/3177861894960065435) */ + { "ex_centerprint", 0, -73 }, + { "ex_bprint", 0, -23 }, + { "ex_sprint", 0, -24 }, + { "ex_finaleFinished", 0, -79 }, + + { "ex_localsound", 0, -80 }, + + { "ex_draw_point", 0, -81 }, + { "ex_draw_line", 0, -82 }, + { "ex_draw_arrow", 0, -83 }, + { "ex_draw_ray", 0, -84 }, + { "ex_draw_circle", 0, -85 }, + { "ex_draw_bounds", 0, -86 }, + { "ex_draw_worldtext", 0, -87 }, + { "ex_draw_sphere", 0, -88 }, + { "ex_draw_cylinder", 0, -89 }, + + { "ex_CheckPlayerEXFlags", 0, -90 }, + { "ex_walkpathtogoal", 0, -91 }, + { "ex_bot_movetopoint", 0, -92 }, + { "ex_bot_followentity", 0, -92 }, + + { NULL, 0, 0 } /* end-of-list. */ +}; + +static void PR_PatchRereleaseBuiltins (void) +{ + const exbuiltin_t *ex = exbuiltins; + dfunction_t *f; + + for ( ; ex->name != NULL; ++ex) + { + f = ED_FindFunction (ex->name); + if (f && f->first_statement == ex->first_statement) + f->first_statement = ex->patch_statement; + } +} + + +/* +=============== +PR_LoadProgs +=============== +*/ +void PR_LoadProgs (void) +{ + int i; + + // flush the non-C variable lookup cache + for (i = 0; i < GEFV_CACHESIZE; i++) + gefvCache[i].field[0] = 0; + + CRC_Init (&pr_crc); + + progs = (dprograms_t *)COM_LoadHunkFile ("progs.dat", NULL); + if (!progs) + Host_Error ("PR_LoadProgs: couldn't load progs.dat"); + Con_DPrintf ("Programs occupy %iK.\n", com_filesize/1024); + + for (i = 0; i < com_filesize; i++) + CRC_ProcessByte (&pr_crc, ((byte *)progs)[i]); + + // byte swap the header + for (i = 0; i < (int) sizeof(*progs) / 4; i++) + ((int *)progs)[i] = LittleLong ( ((int *)progs)[i] ); + + if (progs->version != PROG_VERSION) + Host_Error ("progs.dat has wrong version number (%i should be %i)", progs->version, PROG_VERSION); + if (progs->crc != PROGHEADER_CRC) + Host_Error ("progs.dat system vars have been modified, progdefs.h is out of date"); + + pr_functions = (dfunction_t *)((byte *)progs + progs->ofs_functions); + pr_strings = (char *)progs + progs->ofs_strings; + if (progs->ofs_strings + progs->numstrings >= com_filesize) + Host_Error ("progs.dat strings go past end of file\n"); + + // initialize the strings + pr_numknownstrings = 0; + pr_maxknownstrings = 0; + pr_stringssize = progs->numstrings; + if (pr_knownstrings) + Z_Free ((void *)pr_knownstrings); + pr_knownstrings = NULL; + PR_SetEngineString(""); + + pr_globaldefs = (ddef_t *)((byte *)progs + progs->ofs_globaldefs); + pr_fielddefs = (ddef_t *)((byte *)progs + progs->ofs_fielddefs); + pr_statements = (dstatement_t *)((byte *)progs + progs->ofs_statements); + + pr_global_struct = (globalvars_t *)((byte *)progs + progs->ofs_globals); + pr_globals = (float *)pr_global_struct; + + // byte swap the lumps + for (i = 0; i < progs->numstatements; i++) + { + pr_statements[i].op = LittleShort(pr_statements[i].op); + pr_statements[i].a = LittleShort(pr_statements[i].a); + pr_statements[i].b = LittleShort(pr_statements[i].b); + pr_statements[i].c = LittleShort(pr_statements[i].c); + } + + for (i = 0; i < progs->numfunctions; i++) + { + pr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement); + pr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start); + pr_functions[i].s_name = LittleLong (pr_functions[i].s_name); + pr_functions[i].s_file = LittleLong (pr_functions[i].s_file); + pr_functions[i].numparms = LittleLong (pr_functions[i].numparms); + pr_functions[i].locals = LittleLong (pr_functions[i].locals); + } + + for (i = 0; i < progs->numglobaldefs; i++) + { + pr_globaldefs[i].type = LittleShort (pr_globaldefs[i].type); + pr_globaldefs[i].ofs = LittleShort (pr_globaldefs[i].ofs); + pr_globaldefs[i].s_name = LittleLong (pr_globaldefs[i].s_name); + } + + pr_alpha_supported = false; //johnfitz + + for (i = 0; i < progs->numfielddefs; i++) + { + pr_fielddefs[i].type = LittleShort (pr_fielddefs[i].type); + if (pr_fielddefs[i].type & DEF_SAVEGLOBAL) + Host_Error ("PR_LoadProgs: pr_fielddefs[i].type & DEF_SAVEGLOBAL"); + pr_fielddefs[i].ofs = LittleShort (pr_fielddefs[i].ofs); + pr_fielddefs[i].s_name = LittleLong (pr_fielddefs[i].s_name); + + //johnfitz -- detect alpha support in progs.dat + if (!strcmp(pr_strings + pr_fielddefs[i].s_name,"alpha")) + pr_alpha_supported = true; + //johnfitz + } + + for (i = 0; i < progs->numglobals; i++) + ((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]); + + pr_edict_size = progs->entityfields * 4 + sizeof(edict_t) - sizeof(entvars_t); + // round off to next highest whole word address (esp for Alpha) + // this ensures that pointers in the engine data area are always + // properly aligned + pr_edict_size += sizeof(void *) - 1; + pr_edict_size &= ~(sizeof(void *) - 1); + + PR_PatchRereleaseBuiltins (); + pr_effects_mask = PR_FindSupportedEffects (); +} + + +/* +=============== +PR_Init +=============== +*/ +void PR_Init (void) +{ + Cmd_AddCommand ("edict", ED_PrintEdict_f); + Cmd_AddCommand ("edicts", ED_PrintEdicts); + Cmd_AddCommand ("edictcount", ED_Count); + Cmd_AddCommand ("profile", PR_Profile_f); + Cvar_RegisterVariable (&nomonsters); + Cvar_RegisterVariable (&gamecfg); + Cvar_RegisterVariable (&scratch1); + Cvar_RegisterVariable (&scratch2); + Cvar_RegisterVariable (&scratch3); + Cvar_RegisterVariable (&scratch4); + Cvar_RegisterVariable (&savedgamecfg); + Cvar_RegisterVariable (&saved1); + Cvar_RegisterVariable (&saved2); + Cvar_RegisterVariable (&saved3); + Cvar_RegisterVariable (&saved4); +} + + +edict_t *EDICT_NUM(int n) +{ + if (n < 0 || n >= sv.max_edicts) + Host_Error ("EDICT_NUM: bad number %i", n); + return (edict_t *)((byte *)sv.edicts + (n)*pr_edict_size); +} + +int NUM_FOR_EDICT(edict_t *e) +{ + int b; + + b = (byte *)e - (byte *)sv.edicts; + b = b / pr_edict_size; + + if (b < 0 || b >= sv.num_edicts) + Host_Error ("NUM_FOR_EDICT: bad pointer"); + return b; +} + +//=========================================================================== + + +#define PR_STRING_ALLOCSLOTS 256 + +static void PR_AllocStringSlots (void) +{ + pr_maxknownstrings += PR_STRING_ALLOCSLOTS; + Con_DPrintf2("PR_AllocStringSlots: realloc'ing for %d slots\n", pr_maxknownstrings); + pr_knownstrings = (const char **) Z_Realloc ((void *)pr_knownstrings, pr_maxknownstrings * sizeof(char *)); +} + +const char *PR_GetString (int num) +{ + if (num >= 0 && num < pr_stringssize) + return pr_strings + num; + else if (num < 0 && num >= -pr_numknownstrings) + { + if (!pr_knownstrings[-1 - num]) + { + Host_Error ("PR_GetString: attempt to get a non-existant string %d\n", num); + return ""; + } + return pr_knownstrings[-1 - num]; + } + else + { + Host_Error("PR_GetString: invalid string offset %d\n", num); + return ""; + } +} + +int PR_SetEngineString (const char *s) +{ + int i; + + if (!s) + return 0; +#if 0 /* can't: sv.model_precache & sv.sound_precache points to pr_strings */ + if (s >= pr_strings && s <= pr_strings + pr_stringssize) + Host_Error("PR_SetEngineString: \"%s\" in pr_strings area\n", s); +#else + if (s >= pr_strings && s <= pr_strings + pr_stringssize - 2) + return (int)(s - pr_strings); +#endif + for (i = 0; i < pr_numknownstrings; i++) + { + if (pr_knownstrings[i] == s) + return -1 - i; + } + // new unknown engine string + //Con_DPrintf ("PR_SetEngineString: new engine string %p\n", s); +#if 0 + for (i = 0; i < pr_numknownstrings; i++) + { + if (!pr_knownstrings[i]) + break; + } +#endif +// if (i >= pr_numknownstrings) +// { + if (i >= pr_maxknownstrings) + PR_AllocStringSlots(); + pr_numknownstrings++; +// } + pr_knownstrings[i] = s; + return -1 - i; +} + +int PR_AllocString (int size, char **ptr) +{ + int i; + + if (!size) + return 0; + for (i = 0; i < pr_numknownstrings; i++) + { + if (!pr_knownstrings[i]) + break; + } +// if (i >= pr_numknownstrings) +// { + if (i >= pr_maxknownstrings) + PR_AllocStringSlots(); + pr_numknownstrings++; +// } + pr_knownstrings[i] = (char *)Hunk_AllocName(size, "string"); + if (ptr) + *ptr = (char *) pr_knownstrings[i]; + return -1 - i; +} + diff --git a/Quake/pr_exec.cpp b/Quake/pr_exec.cpp new file mode 100644 index 0000000..e2118b7 --- /dev/null +++ b/Quake/pr_exec.cpp @@ -0,0 +1,653 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "quakedef.hpp" + +typedef struct +{ + int s; + dfunction_t *f; +} prstack_t; + +#define MAX_STACK_DEPTH 64 /* was 32 */ +static prstack_t pr_stack[MAX_STACK_DEPTH]; +static int pr_depth; + +#define LOCALSTACK_SIZE 2048 +static int localstack[LOCALSTACK_SIZE]; +static int localstack_used; + +qboolean pr_trace; +dfunction_t *pr_xfunction; +int pr_xstatement; +int pr_argc; + +static const char *pr_opnames[] = +{ + "DONE", + + "MUL_F", + "MUL_V", + "MUL_FV", + "MUL_VF", + + "DIV", + + "ADD_F", + "ADD_V", + + "SUB_F", + "SUB_V", + + "EQ_F", + "EQ_V", + "EQ_S", + "EQ_E", + "EQ_FNC", + + "NE_F", + "NE_V", + "NE_S", + "NE_E", + "NE_FNC", + + "LE", + "GE", + "LT", + "GT", + + "INDIRECT", + "INDIRECT", + "INDIRECT", + "INDIRECT", + "INDIRECT", + "INDIRECT", + + "ADDRESS", + + "STORE_F", + "STORE_V", + "STORE_S", + "STORE_ENT", + "STORE_FLD", + "STORE_FNC", + + "STOREP_F", + "STOREP_V", + "STOREP_S", + "STOREP_ENT", + "STOREP_FLD", + "STOREP_FNC", + + "RETURN", + + "NOT_F", + "NOT_V", + "NOT_S", + "NOT_ENT", + "NOT_FNC", + + "IF", + "IFNOT", + + "CALL0", + "CALL1", + "CALL2", + "CALL3", + "CALL4", + "CALL5", + "CALL6", + "CALL7", + "CALL8", + + "STATE", + + "GOTO", + + "AND", + "OR", + + "BITAND", + "BITOR" +}; + +const char *PR_GlobalString (int ofs); +const char *PR_GlobalStringNoContents (int ofs); + + +//============================================================================= + +/* +================= +PR_PrintStatement +================= +*/ +static void PR_PrintStatement (dstatement_t *s) +{ + int i; + + if ((unsigned int)s->op < Q_COUNTOF(pr_opnames)) + { + Con_Printf("%s ", pr_opnames[s->op]); + i = strlen(pr_opnames[s->op]); + for ( ; i < 10; i++) + Con_Printf(" "); + } + + if (s->op == OP_IF || s->op == OP_IFNOT) + Con_Printf("%sbranch %i", PR_GlobalString(s->a), s->b); + else if (s->op == OP_GOTO) + { + Con_Printf("branch %i", s->a); + } + else if ((unsigned int)(s->op-OP_STORE_F) < 6) + { + Con_Printf("%s", PR_GlobalString(s->a)); + Con_Printf("%s", PR_GlobalStringNoContents(s->b)); + } + else + { + if (s->a) + Con_Printf("%s", PR_GlobalString(s->a)); + if (s->b) + Con_Printf("%s", PR_GlobalString(s->b)); + if (s->c) + Con_Printf("%s", PR_GlobalStringNoContents(s->c)); + } + Con_Printf("\n"); +} + +/* +============ +PR_StackTrace +============ +*/ +static void PR_StackTrace (void) +{ + int i; + dfunction_t *f; + + if (pr_depth == 0) + { + Con_Printf("\n"); + return; + } + + pr_stack[pr_depth].f = pr_xfunction; + for (i = pr_depth; i >= 0; i--) + { + f = pr_stack[i].f; + if (!f) + { + Con_Printf("\n"); + } + else + { + Con_Printf("%12s : %s\n", PR_GetString(f->s_file), PR_GetString(f->s_name)); + } + } +} + + +/* +============ +PR_Profile_f + +============ +*/ +void PR_Profile_f (void) +{ + int i, num; + int pmax; + dfunction_t *f, *best; + + if (!sv.active) + return; + + num = 0; + do + { + pmax = 0; + best = NULL; + for (i = 0; i < progs->numfunctions; i++) + { + f = &pr_functions[i]; + if (f->profile > pmax) + { + pmax = f->profile; + best = f; + } + } + if (best) + { + if (num < 10) + Con_Printf("%7i %s\n", best->profile, PR_GetString(best->s_name)); + num++; + best->profile = 0; + } + } while (best); +} + + +/* +============ +PR_RunError + +Aborts the currently executing function +============ +*/ +void PR_RunError (const char *error, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr, error); + q_vsnprintf (string, sizeof(string), error, argptr); + va_end (argptr); + + PR_PrintStatement(pr_statements + pr_xstatement); + PR_StackTrace(); + + Con_Printf("%s\n", string); + + pr_depth = 0; // dump the stack so host_error can shutdown functions + + Host_Error("Program error"); +} + +/* +==================== +PR_EnterFunction + +Returns the new program statement counter +==================== +*/ +static int PR_EnterFunction (dfunction_t *f) +{ + int i, j, c, o; + + pr_stack[pr_depth].s = pr_xstatement; + pr_stack[pr_depth].f = pr_xfunction; + pr_depth++; + if (pr_depth >= MAX_STACK_DEPTH) + PR_RunError("stack overflow"); + + // save off any locals that the new function steps on + c = f->locals; + if (localstack_used + c > LOCALSTACK_SIZE) + PR_RunError("PR_ExecuteProgram: locals stack overflow"); + + for (i = 0; i < c ; i++) + localstack[localstack_used + i] = ((int *)pr_globals)[f->parm_start + i]; + localstack_used += c; + + // copy parameters + o = f->parm_start; + for (i = 0; i < f->numparms; i++) + { + for (j = 0; j < f->parm_size[i]; j++) + { + ((int *)pr_globals)[o] = ((int *)pr_globals)[OFS_PARM0 + i*3 + j]; + o++; + } + } + + pr_xfunction = f; + return f->first_statement - 1; // offset the s++ +} + +/* +==================== +PR_LeaveFunction +==================== +*/ +static int PR_LeaveFunction (void) +{ + int i, c; + + if (pr_depth <= 0) + Host_Error("prog stack underflow"); + + // Restore locals from the stack + c = pr_xfunction->locals; + localstack_used -= c; + if (localstack_used < 0) + PR_RunError("PR_ExecuteProgram: locals stack underflow"); + + for (i = 0; i < c; i++) + ((int *)pr_globals)[pr_xfunction->parm_start + i] = localstack[localstack_used + i]; + + // up stack + pr_depth--; + pr_xfunction = pr_stack[pr_depth].f; + return pr_stack[pr_depth].s; +} + + +/* +==================== +PR_ExecuteProgram + +The interpretation main loop +==================== +*/ +#define OPA ((eval_t *)&pr_globals[(unsigned short)st->a]) +#define OPB ((eval_t *)&pr_globals[(unsigned short)st->b]) +#define OPC ((eval_t *)&pr_globals[(unsigned short)st->c]) + +void PR_ExecuteProgram (func_t fnum) +{ + eval_t *ptr; + dstatement_t *st; + dfunction_t *f, *newf; + int profile, startprofile; + edict_t *ed; + int exitdepth; + + if (!fnum || fnum >= progs->numfunctions) + { + if (pr_global_struct->self) + ED_Print (PROG_TO_EDICT(pr_global_struct->self)); + Host_Error ("PR_ExecuteProgram: NULL function"); + } + + f = &pr_functions[fnum]; + + pr_trace = false; + +// make a stack frame + exitdepth = pr_depth; + + st = &pr_statements[PR_EnterFunction(f)]; + startprofile = profile = 0; + + while (1) + { + st++; /* next statement */ + + if (++profile > 0x1000000) /* was 100000 */ + { + pr_xstatement = st - pr_statements; + PR_RunError("runaway loop error"); + } + + if (pr_trace) + PR_PrintStatement(st); + + switch (st->op) + { + case OP_ADD_F: + OPC->_float = OPA->_float + OPB->_float; + break; + case OP_ADD_V: + OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; + break; + + case OP_SUB_F: + OPC->_float = OPA->_float - OPB->_float; + break; + case OP_SUB_V: + OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; + break; + + case OP_MUL_F: + OPC->_float = OPA->_float * OPB->_float; + break; + case OP_MUL_V: + OPC->_float = OPA->vector[0] * OPB->vector[0] + + OPA->vector[1] * OPB->vector[1] + + OPA->vector[2] * OPB->vector[2]; + break; + case OP_MUL_FV: + OPC->vector[0] = OPA->_float * OPB->vector[0]; + OPC->vector[1] = OPA->_float * OPB->vector[1]; + OPC->vector[2] = OPA->_float * OPB->vector[2]; + break; + case OP_MUL_VF: + OPC->vector[0] = OPB->_float * OPA->vector[0]; + OPC->vector[1] = OPB->_float * OPA->vector[1]; + OPC->vector[2] = OPB->_float * OPA->vector[2]; + break; + + case OP_DIV_F: + OPC->_float = OPA->_float / OPB->_float; + break; + + case OP_BITAND: + OPC->_float = (int)OPA->_float & (int)OPB->_float; + break; + + case OP_BITOR: + OPC->_float = (int)OPA->_float | (int)OPB->_float; + break; + + case OP_GE: + OPC->_float = OPA->_float >= OPB->_float; + break; + case OP_LE: + OPC->_float = OPA->_float <= OPB->_float; + break; + case OP_GT: + OPC->_float = OPA->_float > OPB->_float; + break; + case OP_LT: + OPC->_float = OPA->_float < OPB->_float; + break; + case OP_AND: + OPC->_float = OPA->_float && OPB->_float; + break; + case OP_OR: + OPC->_float = OPA->_float || OPB->_float; + break; + + case OP_NOT_F: + OPC->_float = !OPA->_float; + break; + case OP_NOT_V: + OPC->_float = !OPA->vector[0] && !OPA->vector[1] && !OPA->vector[2]; + break; + case OP_NOT_S: + OPC->_float = !OPA->string || !*PR_GetString(OPA->string); + break; + case OP_NOT_FNC: + OPC->_float = !OPA->function; + break; + case OP_NOT_ENT: + OPC->_float = (PROG_TO_EDICT(OPA->edict) == sv.edicts); + break; + + case OP_EQ_F: + OPC->_float = OPA->_float == OPB->_float; + break; + case OP_EQ_V: + OPC->_float = (OPA->vector[0] == OPB->vector[0]) && + (OPA->vector[1] == OPB->vector[1]) && + (OPA->vector[2] == OPB->vector[2]); + break; + case OP_EQ_S: + OPC->_float = !strcmp(PR_GetString(OPA->string), PR_GetString(OPB->string)); + break; + case OP_EQ_E: + OPC->_float = OPA->_int == OPB->_int; + break; + case OP_EQ_FNC: + OPC->_float = OPA->function == OPB->function; + break; + + case OP_NE_F: + OPC->_float = OPA->_float != OPB->_float; + break; + case OP_NE_V: + OPC->_float = (OPA->vector[0] != OPB->vector[0]) || + (OPA->vector[1] != OPB->vector[1]) || + (OPA->vector[2] != OPB->vector[2]); + break; + case OP_NE_S: + OPC->_float = strcmp(PR_GetString(OPA->string), PR_GetString(OPB->string)); + break; + case OP_NE_E: + OPC->_float = OPA->_int != OPB->_int; + break; + case OP_NE_FNC: + OPC->_float = OPA->function != OPB->function; + break; + + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FLD: // integers + case OP_STORE_S: + case OP_STORE_FNC: // pointers + OPB->_int = OPA->_int; + break; + case OP_STORE_V: + OPB->vector[0] = OPA->vector[0]; + OPB->vector[1] = OPA->vector[1]; + OPB->vector[2] = OPA->vector[2]; + break; + + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: // integers + case OP_STOREP_S: + case OP_STOREP_FNC: // pointers + ptr = (eval_t *)((byte *)sv.edicts + OPB->_int); + ptr->_int = OPA->_int; + break; + case OP_STOREP_V: + ptr = (eval_t *)((byte *)sv.edicts + OPB->_int); + ptr->vector[0] = OPA->vector[0]; + ptr->vector[1] = OPA->vector[1]; + ptr->vector[2] = OPA->vector[2]; + break; + + case OP_ADDRESS: + ed = PROG_TO_EDICT(OPA->edict); +#ifdef PARANOID + NUM_FOR_EDICT(ed); // Make sure it's in range +#endif + if (ed == (edict_t *)sv.edicts && sv.state == ss_active) + { + pr_xstatement = st - pr_statements; + PR_RunError("assignment to world entity"); + } + OPC->_int = (byte *)((int *)&ed->v + OPB->_int) - (byte *)sv.edicts; + break; + + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + ed = PROG_TO_EDICT(OPA->edict); +#ifdef PARANOID + NUM_FOR_EDICT(ed); // Make sure it's in range +#endif + OPC->_int = ((eval_t *)((int *)&ed->v + OPB->_int))->_int; + break; + + case OP_LOAD_V: + ed = PROG_TO_EDICT(OPA->edict); +#ifdef PARANOID + NUM_FOR_EDICT(ed); // Make sure it's in range +#endif + ptr = (eval_t *)((int *)&ed->v + OPB->_int); + OPC->vector[0] = ptr->vector[0]; + OPC->vector[1] = ptr->vector[1]; + OPC->vector[2] = ptr->vector[2]; + break; + + case OP_IFNOT: + if (!OPA->_int) + st += st->b - 1; /* -1 to offset the st++ */ + break; + + case OP_IF: + if (OPA->_int) + st += st->b - 1; /* -1 to offset the st++ */ + break; + + case OP_GOTO: + st += st->a - 1; /* -1 to offset the st++ */ + break; + + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: + pr_xfunction->profile += profile - startprofile; + startprofile = profile; + pr_xstatement = st - pr_statements; + pr_argc = st->op - OP_CALL0; + if (!OPA->function) + PR_RunError("NULL function"); + newf = &pr_functions[OPA->function]; + if (newf->first_statement < 0) + { // Built-in function + int i = -newf->first_statement; + if (i >= pr_numbuiltins) + PR_RunError("Bad builtin call number %d", i); + pr_builtins[i](); + break; + } + // Normal function + st = &pr_statements[PR_EnterFunction(newf)]; + break; + + case OP_DONE: + case OP_RETURN: + pr_xfunction->profile += profile - startprofile; + startprofile = profile; + pr_xstatement = st - pr_statements; + pr_globals[OFS_RETURN] = pr_globals[(unsigned short)st->a]; + pr_globals[OFS_RETURN + 1] = pr_globals[(unsigned short)st->a + 1]; + pr_globals[OFS_RETURN + 2] = pr_globals[(unsigned short)st->a + 2]; + st = &pr_statements[PR_LeaveFunction()]; + if (pr_depth == exitdepth) + { // Done + return; + } + break; + + case OP_STATE: + ed = PROG_TO_EDICT(pr_global_struct->self); + ed->v.nextthink = pr_global_struct->time + 0.1; + ed->v.frame = OPA->_float; + ed->v.think = OPB->function; + break; + + default: + pr_xstatement = st - pr_statements; + PR_RunError("Bad opcode %i", st->op); + } + } /* end of while(1) loop */ +} +#undef OPA +#undef OPB +#undef OPC diff --git a/Quake/progdefs.hpp b/Quake/progdefs.hpp new file mode 100644 index 0000000..5a806f5 --- /dev/null +++ b/Quake/progdefs.hpp @@ -0,0 +1,28 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __PROGDEFS_H +#define __PROGDEFS_H + +#include "progdefs.q1" + +#endif /* __PROGDEFS_H */ + diff --git a/Quake/progdefs.q1 b/Quake/progdefs.q1 new file mode 100644 index 0000000..eb15c45 --- /dev/null +++ b/Quake/progdefs.q1 @@ -0,0 +1,143 @@ + +/* file generated by qcc, do not modify */ + +typedef struct +{ int pad[28]; + int self; + int other; + int world; + float time; + float frametime; + float force_retouch; + string_t mapname; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float total_secrets; + float total_monsters; + float found_secrets; + float killed_monsters; + float parm1; + float parm2; + float parm3; + float parm4; + float parm5; + float parm6; + float parm7; + float parm8; + float parm9; + float parm10; + float parm11; + float parm12; + float parm13; + float parm14; + float parm15; + float parm16; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + int trace_ent; + float trace_inopen; + float trace_inwater; + int msg_entity; + func_t main; + func_t StartFrame; + func_t PlayerPreThink; + func_t PlayerPostThink; + func_t ClientKill; + func_t ClientConnect; + func_t PutClientInServer; + func_t ClientDisconnect; + func_t SetNewParms; + func_t SetChangeParms; +} globalvars_t; + +typedef struct +{ + float modelindex; + vec3_t absmin; + vec3_t absmax; + float ltime; + float movetype; + float solid; + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t angles; + vec3_t avelocity; + vec3_t punchangle; + string_t classname; + string_t model; + float frame; + float skin; + float effects; + vec3_t mins; + vec3_t maxs; + vec3_t size; + func_t touch; + func_t use; + func_t think; + func_t blocked; + float nextthink; + int groundentity; + float health; + float frags; + float weapon; + string_t weaponmodel; + float weaponframe; + float currentammo; + float ammo_shells; + float ammo_nails; + float ammo_rockets; + float ammo_cells; + float items; + float takedamage; + int chain; + float deadflag; + vec3_t view_ofs; + float button0; + float button1; + float button2; + float impulse; + float fixangle; + vec3_t v_angle; + float idealpitch; + string_t netname; + int enemy; + float flags; + float colormap; + float team; + float max_health; + float teleport_time; + float armortype; + float armorvalue; + float waterlevel; + float watertype; + float ideal_yaw; + float yaw_speed; + int aiment; + int goalentity; + float spawnflags; + string_t target; + string_t targetname; + float dmg_take; + float dmg_save; + int dmg_inflictor; + int owner; + vec3_t movedir; + string_t message; + float sounds; + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; +} entvars_t; + +#define PROGHEADER_CRC 5927 diff --git a/Quake/progs.hpp b/Quake/progs.hpp new file mode 100644 index 0000000..30a78c5 --- /dev/null +++ b/Quake/progs.hpp @@ -0,0 +1,154 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef QUAKE_PROGS_H +#define QUAKE_PROGS_H + +#include "pr_comp.hpp" /* defs shared with qcc */ +#include "progdefs.hpp" /* generated by program cdefs */ + +typedef union eval_s +{ + string_t string; + float _float; + float vector[3]; + func_t function; + int _int; + int edict; +} eval_t; + +#define MAX_ENT_LEAFS 32 +typedef struct edict_s +{ + qboolean free; + link_t area; /* linked to a division node or leaf */ + + int num_leafs; + int leafnums[MAX_ENT_LEAFS]; + + entity_state_t baseline; + unsigned char alpha; /* johnfitz -- hack to support alpha since it's not part of entvars_t */ + unsigned char scale; /* Quakespasm: added for model scale support. */ + qboolean sendinterval; /* johnfitz -- send time until nextthink to client for better lerp timing */ + float oldframe; + float oldthinktime; + + float freetime; /* sv.time when the object was freed */ + entvars_t v; /* C exported fields from progs */ + + /* other fields from progs come immediately after */ +} edict_t; + +#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area) + +//============================================================================ + +extern dprograms_t *progs; +extern dfunction_t *pr_functions; +extern dstatement_t *pr_statements; +extern globalvars_t *pr_global_struct; +extern float *pr_globals; /* same as pr_global_struct */ + +extern int pr_edict_size; /* in bytes */ + + +void PR_Init (void); + +void PR_ExecuteProgram (func_t fnum); +void PR_LoadProgs (void); + +const char *PR_GetString (int num); +int PR_SetEngineString (const char *s); +int PR_AllocString (int bufferlength, char **ptr); + +void PR_Profile_f (void); + +edict_t *ED_Alloc (void); +void ED_Free (edict_t *ed); + +void ED_Print (edict_t *ed); +void ED_Write (FILE *f, edict_t *ed); +const char *ED_ParseEdict (const char *data, edict_t *ent); + +void ED_WriteGlobals (FILE *f); +const char *ED_ParseGlobals (const char *data); + +void ED_LoadFromFile (const char *data); + +/* +#define EDICT_NUM(n) ((edict_t *)(sv.edicts+ (n)*pr_edict_size)) +#define NUM_FOR_EDICT(e) (((byte *)(e) - sv.edicts) / pr_edict_size) +*/ +edict_t *EDICT_NUM(int); +int NUM_FOR_EDICT(edict_t*); + +#define NEXT_EDICT(e) ((edict_t *)( (byte *)e + pr_edict_size)) + +#define EDICT_TO_PROG(e) (int)((byte *)e - (byte *)sv.edicts) +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) + +#define G_FLOAT(o) (pr_globals[o]) +#define G_INT(o) (*(int *)&pr_globals[o]) +#define G_EDICT(o) ((edict_t *)((byte *)sv.edicts+ *(int *)&pr_globals[o])) +#define G_EDICTNUM(o) NUM_FOR_EDICT(G_EDICT(o)) +#define G_VECTOR(o) (&pr_globals[o]) +#define G_STRING(o) (PR_GetString(*(string_t *)&pr_globals[o])) +#define G_FUNCTION(o) (*(func_t *)&pr_globals[o]) + +#define E_FLOAT(e,o) (((float*)&e->v)[o]) +#define E_INT(e,o) (*(int *)&((float*)&e->v)[o]) +#define E_VECTOR(e,o) (&((float*)&e->v)[o]) +#define E_STRING(e,o) (PR_GetString(*(string_t *)&((float*)&e->v)[o])) + +#define NUM_TYPE_SIZES 8 +extern const int type_size[NUM_TYPE_SIZES]; + +typedef void (*builtin_t) (void); +extern const builtin_t *pr_builtins; +extern const int pr_numbuiltins; + +/* for 2021 re-release */ +typedef struct { + const char *name; + int first_statement; + int patch_statement; +} exbuiltin_t; + +extern int pr_argc; + +extern qboolean pr_trace; +extern dfunction_t *pr_xfunction; +extern int pr_xstatement; + +extern unsigned short pr_crc; + +FUNC_NORETURN void PR_RunError (const char *error, ...) FUNC_PRINTF(1,2); +#ifdef __WATCOMC__ +#pragma aux PR_RunError aborts; +#endif + +void ED_PrintEdicts (void); +void ED_PrintNum (int ent); + +eval_t *GetEdictFieldValue(edict_t *ed, const char *field); + +#endif /* QUAKE_PROGS_H */ diff --git a/Quake/protocol.hpp b/Quake/protocol.hpp new file mode 100644 index 0000000..a155eee --- /dev/null +++ b/Quake/protocol.hpp @@ -0,0 +1,279 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_PROTOCOL_H +#define _QUAKE_PROTOCOL_H + +// protocol.h -- communications protocols + +#define PROTOCOL_NETQUAKE 15 //johnfitz -- standard quake protocol +#define PROTOCOL_FITZQUAKE 666 //johnfitz -- added new protocol for fitzquake 0.85 +#define PROTOCOL_RMQ 999 + +// PROTOCOL_RMQ protocol flags +#define PRFL_SHORTANGLE (1 << 1) +#define PRFL_FLOATANGLE (1 << 2) +#define PRFL_24BITCOORD (1 << 3) +#define PRFL_FLOATCOORD (1 << 4) +#define PRFL_EDICTSCALE (1 << 5) +#define PRFL_ALPHASANITY (1 << 6) // cleanup insanity with alpha +#define PRFL_INT32COORD (1 << 7) +#define PRFL_MOREFLAGS (1 << 31) // not supported + +// if the high bit of the servercmd is set, the low bits are fast update flags: +#define U_MOREBITS (1<<0) +#define U_ORIGIN1 (1<<1) +#define U_ORIGIN2 (1<<2) +#define U_ORIGIN3 (1<<3) +#define U_ANGLE2 (1<<4) +#define U_STEP (1<<5) //johnfitz -- was U_NOLERP, renamed since it's only used for MOVETYPE_STEP +#define U_FRAME (1<<6) +#define U_SIGNAL (1<<7) // just differentiates from other updates + +// svc_update can pass all of the fast update bits, plus more +#define U_ANGLE1 (1<<8) +#define U_ANGLE3 (1<<9) +#define U_MODEL (1<<10) +#define U_COLORMAP (1<<11) +#define U_SKIN (1<<12) +#define U_EFFECTS (1<<13) +#define U_LONGENTITY (1<<14) +//johnfitz -- PROTOCOL_FITZQUAKE -- new bits +#define U_EXTEND1 (1<<15) +#define U_ALPHA (1<<16) // 1 byte, uses ENTALPHA_ENCODE, not sent if equal to baseline +#define U_FRAME2 (1<<17) // 1 byte, this is .frame & 0xFF00 (second byte) +#define U_MODEL2 (1<<18) // 1 byte, this is .modelindex & 0xFF00 (second byte) +#define U_LERPFINISH (1<<19) // 1 byte, 0.0-1.0 maps to 0-255, not sent if exactly 0.1, this is ent->v.nextthink - sv.time, used for lerping +#define U_SCALE (1<<20) // 1 byte, for PROTOCOL_RMQ PRFL_EDICTSCALE +#define U_UNUSED21 (1<<21) +#define U_UNUSED22 (1<<22) +#define U_EXTEND2 (1<<23) // another byte to follow, future expansion +//johnfitz + +//johnfitz -- PROTOCOL_NEHAHRA transparency +#define U_TRANS (1<<15) +//johnfitz + +#define SU_VIEWHEIGHT (1<<0) +#define SU_IDEALPITCH (1<<1) +#define SU_PUNCH1 (1<<2) +#define SU_PUNCH2 (1<<3) +#define SU_PUNCH3 (1<<4) +#define SU_VELOCITY1 (1<<5) +#define SU_VELOCITY2 (1<<6) +#define SU_VELOCITY3 (1<<7) +#define SU_UNUSED8 (1<<8) //AVAILABLE BIT +#define SU_ITEMS (1<<9) +#define SU_ONGROUND (1<<10) // no data follows, the bit is it +#define SU_INWATER (1<<11) // no data follows, the bit is it +#define SU_WEAPONFRAME (1<<12) +#define SU_ARMOR (1<<13) +#define SU_WEAPON (1<<14) +//johnfitz -- PROTOCOL_FITZQUAKE -- new bits +#define SU_EXTEND1 (1<<15) // another byte to follow +#define SU_WEAPON2 (1<<16) // 1 byte, this is .weaponmodel & 0xFF00 (second byte) +#define SU_ARMOR2 (1<<17) // 1 byte, this is .armorvalue & 0xFF00 (second byte) +#define SU_AMMO2 (1<<18) // 1 byte, this is .currentammo & 0xFF00 (second byte) +#define SU_SHELLS2 (1<<19) // 1 byte, this is .ammo_shells & 0xFF00 (second byte) +#define SU_NAILS2 (1<<20) // 1 byte, this is .ammo_nails & 0xFF00 (second byte) +#define SU_ROCKETS2 (1<<21) // 1 byte, this is .ammo_rockets & 0xFF00 (second byte) +#define SU_CELLS2 (1<<22) // 1 byte, this is .ammo_cells & 0xFF00 (second byte) +#define SU_EXTEND2 (1<<23) // another byte to follow +#define SU_WEAPONFRAME2 (1<<24) // 1 byte, this is .weaponframe & 0xFF00 (second byte) +#define SU_WEAPONALPHA (1<<25) // 1 byte, this is alpha for weaponmodel, uses ENTALPHA_ENCODE, not sent if ENTALPHA_DEFAULT +#define SU_UNUSED26 (1<<26) +#define SU_UNUSED27 (1<<27) +#define SU_UNUSED28 (1<<28) +#define SU_UNUSED29 (1<<29) +#define SU_UNUSED30 (1<<30) +#define SU_EXTEND3 (1<<31) // another byte to follow, future expansion +//johnfitz + +// a sound with no channel is a local only sound +#define SND_VOLUME (1<<0) // a byte +#define SND_ATTENUATION (1<<1) // a byte +#define SND_LOOPING (1<<2) // a long + +#define DEFAULT_SOUND_PACKET_VOLUME 255 +#define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 + +//johnfitz -- PROTOCOL_FITZQUAKE -- new bits +#define SND_LARGEENTITY (1<<3) // a short + byte (instead of just a short) +#define SND_LARGESOUND (1<<4) // a short soundindex (instead of a byte) +//johnfitz + +//johnfitz -- PROTOCOL_FITZQUAKE -- flags for entity baseline messages +#define B_LARGEMODEL (1<<0) // modelindex is short instead of byte +#define B_LARGEFRAME (1<<1) // frame is short instead of byte +#define B_ALPHA (1<<2) // 1 byte, uses ENTALPHA_ENCODE, not sent if ENTALPHA_DEFAULT +#define B_SCALE (1<<3) +//johnfitz + +//johnfitz -- PROTOCOL_FITZQUAKE -- alpha encoding +#define ENTALPHA_DEFAULT 0 //entity's alpha is "default" (i.e. water obeys r_wateralpha) -- must be zero so zeroed out memory works +#define ENTALPHA_ZERO 1 //entity is invisible (lowest possible alpha) +#define ENTALPHA_ONE 255 //entity is fully opaque (highest possible alpha) +#define ENTALPHA_ENCODE(a) (((a)==0)?ENTALPHA_DEFAULT:Q_rint(CLAMP(1.0f,(a)*254.0f+1,255.0f))) //server convert to byte to send to client +#define ENTALPHA_DECODE(a) (((a)==ENTALPHA_DEFAULT)?1.0f:((float)(a)-1)/(254)) //client convert to float for rendering +#define ENTALPHA_TOSAVE(a) (((a)==ENTALPHA_DEFAULT)?0.0f:(((a)==ENTALPHA_ZERO)?-1.0f:((float)(a)-1)/(254))) //server convert to float for savegame +//johnfitz + +#define ENTSCALE_DEFAULT 16 // Equivalent to float 1.0f due to byte packing. +#define ENTSCALE_ENCODE(a) ((a) ? ((a) * ENTSCALE_DEFAULT) : ENTSCALE_DEFAULT) // Convert to byte +#define ENTSCALE_DECODE(a) ((float)(a) / ENTSCALE_DEFAULT) // Convert to float for rendering + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 22 + +// game types sent by serverinfo +// these determine which intermission screen plays +#define GAME_COOP 0 +#define GAME_DEATHMATCH 1 + +//================== +// note that there are some defs.qc that mirror to these numbers +// also related to svc_strings[] in cl_parse +//================== + +// +// server to client +// +#define svc_bad 0 +#define svc_nop 1 +#define svc_disconnect 2 +#define svc_updatestat 3 // [byte] [long] +#define svc_version 4 // [long] server version +#define svc_setview 5 // [short] entity number +#define svc_sound 6 // +#define svc_time 7 // [float] server time +#define svc_print 8 // [string] null terminated string +#define svc_stufftext 9 // [string] stuffed into client's console buffer + // the string should be \n terminated +#define svc_setangle 10 // [angle3] set the view angle to this absolute value +#define svc_serverinfo 11 // [long] version + // [string] signon string + // [string]..[0]model cache + // [string]...[0]sounds cache +#define svc_lightstyle 12 // [byte] [string] +#define svc_updatename 13 // [byte] [string] +#define svc_updatefrags 14 // [byte] [short] +#define svc_clientdata 15 // +#define svc_stopsound 16 // +#define svc_updatecolors 17 // [byte] [byte] +#define svc_particle 18 // [vec3] +#define svc_damage 19 +#define svc_spawnstatic 20 +//#define svc_spawnbinary 21 +#define svc_spawnbaseline 22 +#define svc_temp_entity 23 +#define svc_setpause 24 // [byte] on / off +#define svc_signonnum 25 // [byte] used for the signon sequence +#define svc_centerprint 26 // [string] to put in center of the screen +#define svc_killedmonster 27 +#define svc_foundsecret 28 +#define svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten +#define svc_intermission 30 // [string] music +#define svc_finale 31 // [string] music [string] text +#define svc_cdtrack 32 // [byte] track [byte] looptrack +#define svc_sellscreen 33 +#define svc_cutscene 34 + +//johnfitz -- PROTOCOL_FITZQUAKE -- new server messages +#define svc_skybox 37 // [string] name +#define svc_bf 40 +#define svc_fog 41 // [byte] density [byte] red [byte] green [byte] blue [float] time +#define svc_spawnbaseline2 42 // support for large modelindex, large framenum, alpha, using flags +#define svc_spawnstatic2 43 // support for large modelindex, large framenum, alpha, using flags +#define svc_spawnstaticsound2 44 // [coord3] [short] samp [byte] vol [byte] aten +//johnfitz + +// 2021 re-release server messages - see: +// https://steamcommunity.com/sharedfiles/filedetails/?id=2679459726 +#define svc_botchat 38 +#define svc_setviews 45 +#define svc_updateping 46 +#define svc_updatesocial 47 +#define svc_updateplinfo 48 +#define svc_rawprint 49 +#define svc_servervars 50 +#define svc_seq 51 +// Note: svc_achievement has same value as svcdp_effect! +#define svc_achievement 52 // [string] id +#define svc_chat 53 +#define svc_levelcompleted 54 +#define svc_backtolobby 55 +#define svc_localsound 56 + +// +// client to server +// +#define clc_bad 0 +#define clc_nop 1 +#define clc_disconnect 2 +#define clc_move 3 // [usercmd_t] +#define clc_stringcmd 4 // [string] message + +// +// temp entity events +// +#define TE_SPIKE 0 +#define TE_SUPERSPIKE 1 +#define TE_GUNSHOT 2 +#define TE_EXPLOSION 3 +#define TE_TAREXPLOSION 4 +#define TE_LIGHTNING1 5 +#define TE_LIGHTNING2 6 +#define TE_WIZSPIKE 7 +#define TE_KNIGHTSPIKE 8 +#define TE_LIGHTNING3 9 +#define TE_LAVASPLASH 10 +#define TE_TELEPORT 11 +#define TE_EXPLOSION2 12 + +// PGM 01/21/97 +#define TE_BEAM 13 +// PGM 01/21/97 + +typedef struct +{ + vec3_t origin; + vec3_t angles; + unsigned short modelindex; //johnfitz -- was int + unsigned short frame; //johnfitz -- was int + unsigned char colormap; //johnfitz -- was int + unsigned char skin; //johnfitz -- was int + unsigned char alpha; //johnfitz -- added + unsigned char scale; //Quakespasm: for model scale support. + int effects; +} entity_state_t; + +typedef struct +{ + vec3_t viewangles; + +// intended velocities + float forwardmove; + float sidemove; + float upmove; +} usercmd_t; + +#endif /* _QUAKE_PROTOCOL_H */ diff --git a/Quake/q_ctype.hpp b/Quake/q_ctype.hpp new file mode 100644 index 0000000..729f796 --- /dev/null +++ b/Quake/q_ctype.hpp @@ -0,0 +1,99 @@ +/* Locale insensitive ctype.h functions taken from the RPM library - + * RPM is Copyright (c) 1998 by Red Hat Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef Q_CTYPE_H +#define Q_CTYPE_H + +static inline int q_isascii(int c) +{ + return ((c & ~0x7f) == 0); +} + +static inline int q_islower(int c) +{ + return (c >= 'a' && c <= 'z'); +} + +static inline int q_isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +static inline int q_isalpha(int c) +{ + return (q_islower(c) || q_isupper(c)); +} + +static inline int q_isdigit(int c) +{ + return (c >= '0' && c <= '9'); +} + +static inline int q_isxdigit(int c) +{ + return (q_isdigit(c) || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')); +} + +static inline int q_isalnum(int c) +{ + return (q_isalpha(c) || q_isdigit(c)); +} + +static inline int q_isblank(int c) +{ + return (c == ' ' || c == '\t'); +} + +static inline int q_isspace(int c) +{ + switch(c) { + case ' ': case '\t': + case '\n': case '\r': + case '\f': case '\v': return 1; + } + return 0; +} + +static inline int q_isgraph(int c) +{ + return (c > 0x20 && c <= 0x7e); +} + +static inline int q_isprint(int c) +{ + return (c >= 0x20 && c <= 0x7e); +} + +static inline int q_toascii(int c) +{ + return (c & 0x7f); +} + +static inline int q_tolower(int c) +{ + return ((q_isupper(c)) ? (c | ('a' - 'A')) : c); +} + +static inline int q_toupper(int c) +{ + return ((q_islower(c)) ? (c & ~('a' - 'A')) : c); +} + +#endif /* Q_CTYPE_H */ diff --git a/Quake/q_sound.hpp b/Quake/q_sound.hpp new file mode 100644 index 0000000..ded7b5d --- /dev/null +++ b/Quake/q_sound.hpp @@ -0,0 +1,190 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sound.h -- client sound i/o functions + +#ifndef __QUAKE_SOUND__ +#define __QUAKE_SOUND__ + +/* !!! if this is changed, it must be changed in asm_i386.h too !!! */ +typedef struct +{ + int left; + int right; +} portable_samplepair_t; + +typedef struct sfx_s +{ + char name[MAX_QPATH]; + cache_user_t cache; +} sfx_t; + +/* !!! if this is changed, it must be changed in asm_i386.h too !!! */ +typedef struct +{ + int length; + int loopstart; + int speed; + int width; + int stereo; + byte data[1]; /* variable sized */ +} sfxcache_t; + +typedef struct +{ + int channels; + int samples; /* mono samples in buffer */ + int submission_chunk; /* don't mix less than this # */ + int samplepos; /* in mono samples */ + int samplebits; + int signed8; /* device opened for S8 format? (e.g. Amiga AHI) */ + int speed; + unsigned char *buffer; +} dma_t; + +/* !!! if this is changed, it must be changed in asm_i386.h too !!! */ +typedef struct +{ + sfx_t *sfx; /* sfx number */ + int leftvol; /* 0-255 volume */ + int rightvol; /* 0-255 volume */ + int end; /* end time in global paintsamples */ + int pos; /* sample position in sfx */ + int looping; /* where to loop, -1 = no looping */ + int entnum; /* to allow overriding a specific sound */ + int entchannel; + vec3_t origin; /* origin of sound effect */ + vec_t dist_mult; /* distance multiplier (attenuation/clipK) */ + int master_vol; /* 0-255 master volume */ +} channel_t; + +#define WAV_FORMAT_PCM 1 + +typedef struct +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; /* chunk starts this many bytes from file start */ +} wavinfo_t; + +void S_Init (void); +void S_Startup (void); +void S_Shutdown (void); +void S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation); +void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation); +void S_StopSound (int entnum, int entchannel); +void S_StopAllSounds(qboolean clear); +void S_ClearBuffer (void); +void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up); +void S_ExtraUpdate (void); + +void S_BlockSound (void); +void S_UnblockSound (void); + +sfx_t *S_PrecacheSound (const char *sample); +void S_TouchSound (const char *sample); +void S_ClearPrecache (void); +void S_BeginPrecaching (void); +void S_EndPrecaching (void); +void S_PaintChannels (int endtime); +void S_InitPaintChannels (void); + +/* picks a channel based on priorities, empty slots, number of channels */ +channel_t *SND_PickChannel (int entnum, int entchannel); + +/* spatializes a channel */ +void SND_Spatialize (channel_t *ch); + +/* music stream support */ +void S_RawSamples(int samples, int rate, int width, int channels, byte * data, float volume); + /* Expects data in signed 16 bit, or unsigned 8 bit format. */ + +/* initializes cycling through a DMA buffer and returns information on it */ +qboolean SNDDMA_Init(dma_t *dma); + +/* gets the current DMA position */ +int SNDDMA_GetDMAPos(void); + +/* shutdown the DMA xfer. */ +void SNDDMA_Shutdown(void); + +/* validates & locks the dma buffer */ +void SNDDMA_LockBuffer(void); + +/* unlocks the dma buffer / sends sound to the device */ +void SNDDMA_Submit(void); + +/* blocks sound output upon window focus loss */ +void SNDDMA_BlockSound(void); + +/* unblocks the output upon window focus gain */ +void SNDDMA_UnblockSound(void); + +/* ==================================================================== + * User-setable variables + * ==================================================================== + */ + +#define MAX_CHANNELS 1024 // ericw -- was 512 /* johnfitz -- was 128 */ +#define MAX_DYNAMIC_CHANNELS 128 /* johnfitz -- was 8 */ + +extern channel_t snd_channels[MAX_CHANNELS]; +/* 0 to MAX_DYNAMIC_CHANNELS-1 = normal entity sounds + * MAX_DYNAMIC_CHANNELS to MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS -1 = water, etc + * MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS to total_channels = static sounds + */ + +extern volatile dma_t *shm; + +extern int total_channels; +extern int soundtime; +extern int paintedtime; +extern int s_rawend; + +extern vec3_t listener_origin; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; + +extern cvar_t sndspeed; +extern cvar_t snd_mixspeed; +extern cvar_t snd_filterquality; +extern cvar_t sfxvolume; +extern cvar_t loadas8bit; + +#define MAX_RAW_SAMPLES 8192 +extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + +extern cvar_t bgmvolume; + +void S_LocalSound (const char *name); +sfxcache_t *S_LoadSound (sfx_t *s); + +wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength); + +void SND_InitScaletable (void); + +#endif /* __QUAKE_SOUND__ */ + diff --git a/Quake/q_stdinc.hpp b/Quake/q_stdinc.hpp new file mode 100644 index 0000000..b0b141b --- /dev/null +++ b/Quake/q_stdinc.hpp @@ -0,0 +1,252 @@ +/* q_stdinc.h - includes the minimum necessary stdc headers, + * defines common and / or missing types. + * + * NOTE: for net stuff use net_sys.h, + * for byte order use q_endian.h, + * for math stuff use mathlib.h, + * for locale-insensitive ctype.h functions use q_ctype.h. + * + * Copyright (C) 1996-1997 Id Software, Inc. + * Copyright (C) 2007-2011 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QSTDINC_H +#define QSTDINC_H + +#include +#include +#include +#ifndef _WIN32 /* others we support without sys/param.h? */ +#include +#endif + +#include + +/* NOTES on TYPE SIZES: + Quake/Hexen II engine relied on 32 bit int type size + with ILP32 (not LP32) model in mind. We now support + LP64 and LLP64, too. We expect: + sizeof (char) == 1 + sizeof (short) == 2 + sizeof (int) == 4 + sizeof (float) == 4 + sizeof (long) == 4 / 8 + sizeof (pointer *) == 4 / 8 + For this, we need stdint.h (or inttypes.h) + FIXME: On some platforms, only inttypes.h is available. + FIXME: Properly replace certain short and int usage + with int16_t and int32_t. + */ +#if defined(_MSC_VER) && (_MSC_VER < 1600) +/* MS Visual Studio provides stdint.h only starting with + * version 2010. Even in VS2010, there is no inttypes.h.. */ +#include "msinttypes/stdint.h" +#else +#include +#endif + +#include +#include +#include +#include + +/*==========================================================================*/ + +#ifndef NULL +#if defined(__cplusplus) +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#define Q_MAXCHAR ((char)0x7f) +#define Q_MAXSHORT ((short)0x7fff) +#define Q_MAXINT ((int)0x7fffffff) +#define Q_MAXLONG ((int)0x7fffffff) + +#define Q_MINCHAR ((char)0x80) +#define Q_MINSHORT ((short)0x8000) +#define Q_MININT ((int)0x80000000) +#define Q_MINLONG ((int)0x80000000) + +#ifndef COMPILE_TIME_ASSERT +#if defined(__cplusplus) +/* Keep C++ case alone: Some versions of gcc will define __STDC_VERSION__ even when compiling in C++ mode. */ +#if (__cplusplus >= 201103L) +#define COMPILE_TIME_ASSERT(name, x) static_assert(x, #x) +#endif +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L) +#define COMPILE_TIME_ASSERT(name, x) static_assert(x, #x) +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#define COMPILE_TIME_ASSERT(name, x) _Static_assert(x, #x) +#endif +#endif /**/ +#ifndef COMPILE_TIME_ASSERT +/* universal, but may trigger -Wunused-local-typedefs */ +#define COMPILE_TIME_ASSERT(name, x) \ + typedef int dummy_ ## name[(x) * 2 - 1] +#endif + +COMPILE_TIME_ASSERT(char, sizeof(char) == 1); +COMPILE_TIME_ASSERT(float, sizeof(float) == 4); +COMPILE_TIME_ASSERT(long, sizeof(long) >= 4); +COMPILE_TIME_ASSERT(int, sizeof(int) == 4); +COMPILE_TIME_ASSERT(short, sizeof(short) == 2); + +/* make sure enums are the size of ints for structure packing */ +typedef enum { + THE_DUMMY_VALUE +} THE_DUMMY_ENUM; +COMPILE_TIME_ASSERT(enum, sizeof(THE_DUMMY_ENUM) == sizeof(int)); + + +/* for array size: */ +#define Q_COUNTOF(x) (sizeof(x) / sizeof((x)[0])) + +/* Provide a substitute for offsetof() if we don't have one. + * This variant works on most (but not *all*) systems... + */ +#ifndef offsetof +#define offsetof(t,m) ((intptr_t)&(((t *)0)->m)) +#endif + + +/*==========================================================================*/ + +typedef unsigned char byte; + +/* some structures have qboolean members and the x86 asm code expect + * those members to be 4 bytes long. i.e.: qboolean must be 32 bits. */ +typedef int qboolean; +#undef true +#undef false +#if !defined(__cplusplus) +#if defined __STDC_VERSION__ && (__STDC_VERSION__ >= 199901L) +#include +#else +enum { + false = 0, + true = 1 +}; +#endif +#endif /* */ +COMPILE_TIME_ASSERT(falsehood, ((1 != 1) == false)); +COMPILE_TIME_ASSERT(truth, ((1 == 1) == true)); +COMPILE_TIME_ASSERT(qboolean, sizeof(qboolean) == 4); + +/*==========================================================================*/ + +/* math */ +typedef float vec_t; +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; + + +/*==========================================================================*/ + +/* MAX_OSPATH (max length of a filesystem pathname, i.e. PATH_MAX) + * Note: See GNU Hurd and others' notes about brokenness of this: + * http://www.gnu.org/software/hurd/community/gsoc/project_ideas/maxpath.html + * http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html */ + +#if !defined(PATH_MAX) +/* equivalent values? */ +#if defined(MAXPATHLEN) +#define PATH_MAX MAXPATHLEN +#elif defined(_WIN32) && defined(_MAX_PATH) +#define PATH_MAX _MAX_PATH +#elif defined(_WIN32) && defined(MAX_PATH) +#define PATH_MAX MAX_PATH +#else /* fallback */ +#define PATH_MAX 1024 +#endif +#endif /* PATH_MAX */ + +#define MAX_OSPATH PATH_MAX + +/*==========================================================================*/ + +/* missing types: */ +#if defined(_MSC_VER) +typedef ptrdiff_t ssize_t; +#endif + +/*==========================================================================*/ + +/* function attributes, etc */ + +#if defined(__GNUC__) +#define FUNC_PRINTF(x,y) __attribute__((__format__(__printf__,x,y))) +#else +#define FUNC_PRINTF(x,y) +#endif + +/* argument format attributes for function pointers are supported for gcc >= 3.1 */ +#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 0)) +#define FUNCP_PRINTF FUNC_PRINTF +#else +#define FUNCP_PRINTF(x,y) +#endif + +/* llvm's optnone function attribute started with clang-3.5.0 */ +#if defined(__clang__) && \ + (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 5)) +#define FUNC_NO_OPTIMIZE __attribute__((__optnone__)) +/* function optimize attribute is added starting with gcc 4.4.0 */ +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 3)) +#define FUNC_NO_OPTIMIZE __attribute__((__optimize__("0"))) +#else +#define FUNC_NO_OPTIMIZE +#endif + +#if defined(__GNUC__) +#define FUNC_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) +#define FUNC_NORETURN __declspec(noreturn) +#elif defined(__WATCOMC__) +#define FUNC_NORETURN /* use the 'aborts' aux pragma */ +#else +#define FUNC_NORETURN +#endif + +#if defined(__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define FUNC_NOINLINE __attribute__((__noinline__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1300) +#define FUNC_NOINLINE __declspec(noinline) +#else +#define FUNC_NOINLINE +#endif + +#if defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define FUNC_NOCLONE __attribute__((__noclone__)) +#else +#define FUNC_NOCLONE +#endif + +#if defined(_MSC_VER) && !defined(__cplusplus) +#define inline __inline +#endif /* _MSC_VER */ + +/*==========================================================================*/ + +#endif /* QSTDINC_H */ diff --git a/Quake/qs_bmp.hpp b/Quake/qs_bmp.hpp new file mode 100644 index 0000000..fd2b2b5 --- /dev/null +++ b/Quake/qs_bmp.hpp @@ -0,0 +1,189 @@ +0x42, 0x4d, 0xc6, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x02, 0x00, 0x00, 0x28, 0x00, +0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0xa4, 0x00, +0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06, 0x00, 0x05, 0x06, 0x07, 0x00, 0x06, 0x06, +0x07, 0x00, 0x07, 0x06, 0x07, 0x00, 0x06, 0x07, 0x06, 0x00, 0x06, 0x07, 0x07, 0x00, 0x07, 0x07, +0x07, 0x00, 0x07, 0x08, 0x07, 0x00, 0x08, 0x08, 0x08, 0x00, 0x07, 0x08, 0x09, 0x00, 0x08, 0x08, +0x09, 0x00, 0x07, 0x09, 0x08, 0x00, 0x08, 0x09, 0x08, 0x00, 0x09, 0x09, 0x08, 0x00, 0x08, 0x09, +0x09, 0x00, 0x09, 0x09, 0x09, 0x00, 0x08, 0x09, 0x0a, 0x00, 0x09, 0x09, 0x0a, 0x00, 0x09, 0x09, +0x0b, 0x00, 0x09, 0x0a, 0x09, 0x00, 0x08, 0x0a, 0x0a, 0x00, 0x09, 0x0a, 0x0a, 0x00, 0x0a, 0x0a, +0x0a, 0x00, 0x09, 0x0a, 0x0b, 0x00, 0x0a, 0x0a, 0x0b, 0x00, 0x09, 0x0a, 0x0c, 0x00, 0x09, 0x0b, +0x0b, 0x00, 0x0a, 0x0b, 0x0b, 0x00, 0x0a, 0x0b, 0x0c, 0x00, 0x0b, 0x0b, 0x0c, 0x00, 0x0a, 0x0b, +0x0d, 0x00, 0x0b, 0x0c, 0x0c, 0x00, 0x0b, 0x0c, 0x0d, 0x00, 0x0a, 0x0c, 0x0e, 0x00, 0x0b, 0x0c, +0x0e, 0x00, 0x0c, 0x0c, 0x0f, 0x00, 0x0b, 0x0d, 0x0d, 0x00, 0x0b, 0x0d, 0x0e, 0x00, 0x0c, 0x0d, +0x0e, 0x00, 0x0c, 0x0d, 0x0f, 0x00, 0x0d, 0x0d, 0x0f, 0x00, 0x0b, 0x0d, 0x10, 0x00, 0x0c, 0x0d, +0x10, 0x00, 0x0c, 0x0e, 0x0f, 0x00, 0x0d, 0x0e, 0x0f, 0x00, 0x0c, 0x0e, 0x10, 0x00, 0x0d, 0x0e, +0x10, 0x00, 0x0b, 0x0e, 0x11, 0x00, 0x0c, 0x0e, 0x11, 0x00, 0x0d, 0x0e, 0x11, 0x00, 0x0d, 0x0f, +0x10, 0x00, 0x0d, 0x0f, 0x11, 0x00, 0x0e, 0x0f, 0x11, 0x00, 0x0d, 0x0f, 0x12, 0x00, 0x0e, 0x0f, +0x12, 0x00, 0x0d, 0x0f, 0x13, 0x00, 0x0e, 0x10, 0x11, 0x00, 0x0d, 0x10, 0x12, 0x00, 0x0b, 0x10, +0x13, 0x00, 0x0e, 0x10, 0x12, 0x00, 0x0e, 0x10, 0x13, 0x00, 0x0d, 0x10, 0x14, 0x00, 0x0e, 0x10, +0x14, 0x00, 0x0d, 0x11, 0x13, 0x00, 0x0e, 0x11, 0x13, 0x00, 0x0f, 0x11, 0x13, 0x00, 0x0e, 0x11, +0x14, 0x00, 0x0f, 0x11, 0x14, 0x00, 0x0f, 0x11, 0x15, 0x00, 0x0f, 0x12, 0x14, 0x00, 0x0f, 0x12, +0x15, 0x00, 0x10, 0x12, 0x15, 0x00, 0x0f, 0x12, 0x16, 0x00, 0x10, 0x12, 0x16, 0x00, 0x0f, 0x12, +0x17, 0x00, 0x0f, 0x13, 0x16, 0x00, 0x10, 0x13, 0x16, 0x00, 0x10, 0x13, 0x17, 0x00, 0x11, 0x13, +0x17, 0x00, 0x11, 0x13, 0x18, 0x00, 0x10, 0x14, 0x17, 0x00, 0x11, 0x14, 0x17, 0x00, 0x10, 0x14, +0x18, 0x00, 0x12, 0x15, 0x18, 0x00, 0x10, 0x15, 0x19, 0x00, 0x12, 0x15, 0x19, 0x00, 0x10, 0x15, +0x1a, 0x00, 0x11, 0x15, 0x1a, 0x00, 0x12, 0x15, 0x1a, 0x00, 0x12, 0x15, 0x1b, 0x00, 0x11, 0x16, +0x1a, 0x00, 0x12, 0x16, 0x1a, 0x00, 0x13, 0x16, 0x1a, 0x00, 0x12, 0x16, 0x1b, 0x00, 0x13, 0x16, +0x1b, 0x00, 0x12, 0x17, 0x1b, 0x00, 0x13, 0x17, 0x1b, 0x00, 0x13, 0x17, 0x1c, 0x00, 0x14, 0x17, +0x1c, 0x00, 0x14, 0x17, 0x1d, 0x00, 0x13, 0x18, 0x1d, 0x00, 0x14, 0x18, 0x1d, 0x00, 0x15, 0x18, +0x1f, 0x00, 0x14, 0x19, 0x1d, 0x00, 0x15, 0x19, 0x1d, 0x00, 0x15, 0x19, 0x1e, 0x00, 0x15, 0x19, +0x1f, 0x00, 0x16, 0x19, 0x1f, 0x00, 0x15, 0x19, 0x20, 0x00, 0x16, 0x1a, 0x1e, 0x00, 0x14, 0x1a, +0x1f, 0x00, 0x15, 0x1a, 0x1f, 0x00, 0x16, 0x1a, 0x1f, 0x00, 0x15, 0x1a, 0x20, 0x00, 0x16, 0x1a, +0x21, 0x00, 0x15, 0x1b, 0x21, 0x00, 0x17, 0x1b, 0x22, 0x00, 0x17, 0x1b, 0x23, 0x00, 0x16, 0x1c, +0x22, 0x00, 0x17, 0x1d, 0x22, 0x00, 0x17, 0x1d, 0x23, 0x00, 0x18, 0x1d, 0x23, 0x00, 0x19, 0x1d, +0x23, 0x00, 0x17, 0x1d, 0x24, 0x00, 0x18, 0x1e, 0x23, 0x00, 0x18, 0x1e, 0x24, 0x00, 0x18, 0x1e, +0x25, 0x00, 0x19, 0x1e, 0x25, 0x00, 0x18, 0x1e, 0x26, 0x00, 0x19, 0x1f, 0x26, 0x00, 0x1a, 0x1f, +0x26, 0x00, 0x1a, 0x1f, 0x27, 0x00, 0x1a, 0x1f, 0x28, 0x00, 0x1a, 0x20, 0x26, 0x00, 0x19, 0x20, +0x27, 0x00, 0x1a, 0x20, 0x27, 0x00, 0x1a, 0x20, 0x28, 0x00, 0x1b, 0x20, 0x29, 0x00, 0x1b, 0x21, +0x28, 0x00, 0x1b, 0x21, 0x29, 0x00, 0x1c, 0x21, 0x2a, 0x00, 0x1d, 0x22, 0x29, 0x00, 0x1c, 0x22, +0x2a, 0x00, 0x1d, 0x22, 0x2a, 0x00, 0x1e, 0x23, 0x2c, 0x00, 0x1d, 0x24, 0x2b, 0x00, 0x1d, 0x24, +0x2c, 0x00, 0x1c, 0x24, 0x2d, 0x00, 0x1e, 0x25, 0x2d, 0x00, 0x1f, 0x25, 0x2e, 0x00, 0x1e, 0x26, +0x2e, 0x00, 0x1f, 0x26, 0x2e, 0x00, 0x1f, 0x26, 0x30, 0x00, 0x21, 0x27, 0x2e, 0x00, 0x20, 0x27, +0x2f, 0x00, 0x20, 0x27, 0x31, 0x00, 0x20, 0x29, 0x32, 0x00, 0x20, 0x29, 0x33, 0x00, 0x21, 0x2a, +0x33, 0x00, 0x24, 0x2c, 0x35, 0x00, 0x24, 0x2c, 0x38, 0x00, 0x26, 0x31, 0x3b, 0x00, 0x27, 0x31, +0x3b, 0x00, 0xff, 0x00, 0xff, 0x00, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x05, 0x1a, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x25, 0x48, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4a, 0x54, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x8a, 0x8c, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x41, 0x5e, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x0d, 0x27, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x1b, 0x1f, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x00, 0x32, 0x69, 0x29, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x21, 0x38, 0x63, 0x2b, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x3d, 0x2b, 0x1c, 0x0e, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5d, 0x7e, 0x2b, 0x08, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5d, 0x80, 0x4d, 0x4c, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x24, 0x4a, 0x9c, 0xa1, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x10, 0x39, 0x76, 0x46, 0x6d, 0x9a, +0x60, 0x3f, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x01, 0x78, 0x8e, 0x32, 0x37, 0x81, 0x6c, 0x2a, 0x3c, +0x55, 0x2c, 0x1d, 0x2d, 0x17, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x06, 0x27, 0x55, 0x82, 0x9b, 0x45, 0x0e, 0x47, 0x61, 0x0f, 0x1a, +0x0f, 0x20, 0x62, 0x28, 0x0f, 0x18, 0x08, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0x35, 0x1b, 0x40, 0x98, 0x4d, 0x18, 0x15, 0x1c, 0x26, 0x1b, 0x3b, 0x49, +0x20, 0x46, 0x67, 0x1b, 0x13, 0x0c, 0x15, 0x20, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0x25, 0x4d, 0x3c, 0x34, 0x2b, 0x1c, 0x08, 0x0b, 0x0f, 0x1b, 0x32, 0x9e, 0x8a, +0x08, 0x27, 0x22, 0x0f, 0x36, 0x6f, 0x75, 0x3b, 0x39, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0x7d, 0x7a, 0x56, 0x48, 0x68, 0x32, 0x06, 0xa3, 0xa3, 0xa3, 0x14, 0x40, 0x86, 0x6e, +0xa3, 0xa3, 0xa3, 0x06, 0x2d, 0x85, 0x92, 0x5b, 0x32, 0x6a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0x1c, 0x58, 0x8a, 0x65, 0x18, 0x21, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x07, 0x43, 0x83, 0x43, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x73, 0x91, 0x7f, 0x62, 0x20, 0x15, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x1e, 0x31, 0x0f, 0x2e, 0x1c, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x1b, 0x1b, 0x32, 0x16, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x43, 0x3b, 0x22, 0x55, 0x47, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x72, 0x9d, 0x60, 0x33, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2d, 0x0f, 0x1b, 0x0f, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x11, 0x2d, 0x93, 0x55, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x50, +0x44, 0x90, 0x8a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x3a, 0x77, 0x88, 0x47, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x25, 0x45, 0x25, 0x25, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x8a, +0x51, 0x23, 0x43, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x57, 0x5a, 0xa0, 0x84, 0x87, +0x74, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x02, 0x08, 0x8f, 0x8b, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0x96, +0x6a, 0x41, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4e, 0x95, 0x68, 0x5a, 0x4b, 0x7b, +0x70, 0x4c, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x04, 0x53, 0x8d, 0x79, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x79, 0x80, +0x5b, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x0e, 0x7c, 0x94, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4f, 0x6a, +0x47, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5f, 0x40, 0x1b, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x46, 0x2d, +0x08, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x97, 0x82, 0x18, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x58, 0x43, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x69, 0x5c, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x71, 0x40, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x65, 0x60, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x52, 0x2d, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x66, 0x5e, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2d, 0x20, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x6f, 0x6b, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2b, 0x5b, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2e, 0x44, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x25, 0x41, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x40, 0x1e, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x10, 0x38, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x69, 0x42, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x3b, +0x19, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x03, 0x20, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x53, +0x1d, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x4f, 0x15, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x1d, 0x09, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x48, 0x89, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0x1e, 0x33, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x5b, 0x39, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0x3b, 0x2a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x59, 0x9f, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0x33, 0x0a, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x2f, 0x64, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0x1d, 0x2b, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x12, 0x99, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0x49, 0x3e, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x1c, 0x36, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x30, 0x5e, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0x8a, 0x48, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, +0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3 diff --git a/Quake/quakedef.hpp b/Quake/quakedef.hpp new file mode 100644 index 0000000..bc773c4 --- /dev/null +++ b/Quake/quakedef.hpp @@ -0,0 +1,336 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2019 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef QUAKEDEFS_H +#define QUAKEDEFS_H + +// quakedef.h -- primary header for client + +#define QUAKE_GAME // as opposed to utilities + +#define VERSION 1.09 +#define GLQUAKE_VERSION 1.00 +#define D3DQUAKE_VERSION 0.01 +#define WINQUAKE_VERSION 0.996 +#define LINUX_VERSION 1.30 +#define X11_VERSION 1.10 + +#define FITZQUAKE_VERSION 0.85 //johnfitz +#define QUAKESPASM_VERSION 0.96 +#define QUAKESPASM_VER_PATCH 3 // helper to print a string like 0.94.7 +#ifndef QUAKESPASM_VER_SUFFIX +#define QUAKESPASM_VER_SUFFIX // optional version suffix string literal like "-beta1" +#endif + +#define QS_STRINGIFY_(x) #x +#define QS_STRINGIFY(x) QS_STRINGIFY_(x) + +// combined version string like "0.92.1-beta1" +#define QUAKESPASM_VER_STRING QS_STRINGIFY(QUAKESPASM_VERSION) "." QS_STRINGIFY(QUAKESPASM_VER_PATCH) QUAKESPASM_VER_SUFFIX + +//define PARANOID // speed sapping error checking + +#define GAMENAME "id1" // directory to look in by default + +#include "q_stdinc.hpp" + +// !!! if this is changed, it must be changed in d_ifacea.h too !!! +#define CACHE_SIZE 32 // used to align key data structures + +#define Q_UNUSED(x) (x = x) // for pesky compiler / lint warnings + +#define MINIMUM_MEMORY 0x550000 +#define MINIMUM_MEMORY_LEVELPAK (MINIMUM_MEMORY + 0x100000) + +#define MAX_NUM_ARGVS 50 + +// up / down +#define PITCH 0 + +// left / right +#define YAW 1 + +// fall over +#define ROLL 2 + + +#define MAX_QPATH 64 // max length of a quake game pathname + +#define ON_EPSILON 0.1 // point on plane side epsilon + +#define DIST_EPSILON (0.03125) // 1/32 epsilon to keep floating point happy (moved from world.c) + +#define MAX_MSGLEN 64000 // max length of a reliable message //ericw -- was 32000 +#define MAX_DATAGRAM 64000 // max length of unreliable message //johnfitz -- was 1024 + +#define DATAGRAM_MTU 1400 // johnfitz -- actual limit for unreliable messages to nonlocal clients + +// +// per-level limits +// +#define MIN_EDICTS 256 // johnfitz -- lowest allowed value for max_edicts cvar +#define MAX_EDICTS 32000 // johnfitz -- highest allowed value for max_edicts cvar + // ents past 8192 can't play sounds in the standard protocol +#define MAX_LIGHTSTYLES 64 +#define MAX_MODELS 2048 // johnfitz -- was 256 +#define MAX_SOUNDS 2048 // johnfitz -- was 256 + +#define SAVEGAME_COMMENT_LENGTH 39 + +#define MAX_STYLESTRING 64 + +// +// stats are integers communicated to the client by the server +// +#define MAX_CL_STATS 32 +#define STAT_HEALTH 0 +#define STAT_FRAGS 1 +#define STAT_WEAPON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR 4 +#define STAT_WEAPONFRAME 5 +#define STAT_SHELLS 6 +#define STAT_NAILS 7 +#define STAT_ROCKETS 8 +#define STAT_CELLS 9 +#define STAT_ACTIVEWEAPON 10 +#define STAT_TOTALSECRETS 11 +#define STAT_TOTALMONSTERS 12 +#define STAT_SECRETS 13 // bumped on client side by svc_foundsecret +#define STAT_MONSTERS 14 // bumped by svc_killedmonster + +// stock defines +// +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_SUPER_LIGHTNING 128 +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 +#define IT_AXE 4096 +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 +#define IT_KEY1 131072 +#define IT_KEY2 262144 +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_SIGIL1 (1<<28) +#define IT_SIGIL2 (1<<29) +#define IT_SIGIL3 (1<<30) +#define IT_SIGIL4 (1<<31) + +//=========================================== +//rogue changed and added defines + +#define RIT_SHELLS 128 +#define RIT_NAILS 256 +#define RIT_ROCKETS 512 +#define RIT_CELLS 1024 +#define RIT_AXE 2048 +#define RIT_LAVA_NAILGUN 4096 +#define RIT_LAVA_SUPER_NAILGUN 8192 +#define RIT_MULTI_GRENADE 16384 +#define RIT_MULTI_ROCKET 32768 +#define RIT_PLASMA_GUN 65536 +#define RIT_ARMOR1 8388608 +#define RIT_ARMOR2 16777216 +#define RIT_ARMOR3 33554432 +#define RIT_LAVA_NAILS 67108864 +#define RIT_PLASMA_AMMO 134217728 +#define RIT_MULTI_ROCKETS 268435456 +#define RIT_SHIELD 536870912 +#define RIT_ANTIGRAV 1073741824 +#define RIT_SUPERHEALTH 2147483648 + +//MED 01/04/97 added hipnotic defines +//=========================================== +//hipnotic added defines +#define HIT_PROXIMITY_GUN_BIT 16 +#define HIT_MJOLNIR_BIT 7 +#define HIT_LASER_CANNON_BIT 23 +#define HIT_PROXIMITY_GUN (1< +#include +#else +#include +#include +#endif +#else +#include "SDL.h" +#include "SDL_opengl.h" +#endif +#ifndef APIENTRY +#define APIENTRY +#endif + +#include "console.hpp" +#include "wad.hpp" +#include "vid.hpp" +#include "screen.hpp" +#include "draw.hpp" +#include "render.hpp" +#include "view.hpp" +#include "sbar.hpp" +#include "q_sound.hpp" +#include "client.hpp" + +#include "gl_model.hpp" +#include "world.hpp" + +#include "image.hpp" //johnfitz +#include "gl_texmgr.hpp" //johnfitz +#include "input.hpp" +#include "keys.hpp" +#include "menu.hpp" +#include "cdaudio.hpp" +#include "glquake.hpp" + + +//============================================================================= + +// the host system specifies the base of the directory tree, the +// command line parms passed to the program, and the amount of memory +// available for the program to use + +extern qboolean noclip_anglehack; + +// +// host +// +extern quakeparms_t *host_parms; + +extern cvar_t sys_ticrate; +extern cvar_t sys_throttle; +extern cvar_t sys_nostdout; +extern cvar_t developer; +extern cvar_t max_edicts; //johnfitz + +extern qboolean host_initialized; // true if into command execution +extern double host_frametime; +extern byte *host_colormap; +extern int host_framecount; // incremented every frame, never reset +extern double realtime; // not bounded in any way, changed at + // start of every frame, never reset + +typedef struct filelist_item_s +{ + char name[32]; + struct filelist_item_s *next; +} filelist_item_t; + +extern filelist_item_t *modlist; +extern filelist_item_t *extralevels; +extern filelist_item_t *demolist; + +void Host_ClearMemory (void); +void Host_ServerFrame (void); +void Host_InitCommands (void); +void Host_Init (void); +void Host_Shutdown(void); +void Host_Callback_Notify (cvar_t *var); /* callback function for CVAR_NOTIFY */ +FUNC_NORETURN void Host_Error (const char *error, ...) FUNC_PRINTF(1,2); +FUNC_NORETURN void Host_EndGame (const char *message, ...) FUNC_PRINTF(1,2); +#ifdef __WATCOMC__ +#pragma aux Host_Error aborts; +#pragma aux Host_EndGame aborts; +#endif +void Host_Frame (float time); +void Host_Quit_f (void); +void Host_ClientCommands (const char *fmt, ...) FUNC_PRINTF(1,2); +void Host_ShutdownServer (qboolean crash); +void Host_WriteConfiguration (void); +void Host_Resetdemos (void); + +void ExtraMaps_Init (void); +void Modlist_Init (void); +void DemoList_Init (void); + +void ExtraMaps_NewGame (void); +void DemoList_Rebuild (void); + +extern int current_skill; // skill level for currently loaded level (in case + // the user changes the cvar while the level is + // running, this reflects the level actually in use) + +extern qboolean isDedicated; + +extern int minimum_memory; + +#endif /* QUAKEDEFS_H */ + diff --git a/Quake/quakespasm.pak b/Quake/quakespasm.pak new file mode 100644 index 0000000000000000000000000000000000000000..820524b51a4b81d78ccc68f96079cd3542812efd GIT binary patch literal 558464 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 z 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%0A* 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%0A&o8@h7O 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

JN{B()u_?0Zc-? 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

r6(bV%DxPxop59g=H0aSwH zK|FO5V#I@bMzj>qvRYcfFZ054a+lfn1&SqO4iAGS(u}$=P=lZ1hlxXB7^Tx^P)ioU zt}rd}=RfcbsSqnjRhqsiPa|QCledL?B#HPwBYYPaFav=$8>#Z=M}SICO%;L5q5Cer zuAO(I%ODJA7{CUc@lKPubj`ok3r2kL4dL54l)$~aQL?u9hx^55I>gs{00`BNe$t&N&;no}Ffe8i!d2A4UcfN5P{IR#j#}Y|Fgl9DTt?zn4?Bo3?8dLo32c;d5*LJcYj;!mhH&I-wdHc--r zT}hONBShD3t-(5eE;SLZkicFQSmA*j>=^Mylnm;JAM-g#T8StW6~f8_gcK3qcoRWa zxOo=d^pwDgOi}i=oS|LfAJzYbV$zI!MaJXWyri+xpd)U?kH0n)_6<9h^i_fB|60Xs zFf#Z;yU_KokOS^~7=Iprp`*<~urWw9Yrw(fL^A!S9qLj*%<+(5^{|kLS`ICNLEs{7 zWX&Nhu$U&zhaFSB)szafow{6GpCT2%frU9rfE0%N@z~I*h)Ur~eBVYGG9-#Zp14)a z3@5X?me@!h*9C+bCyB%udw|NkqBx^lZWYa;<=Q#{heL$b>xX>C&gcy3Hi@=yeLcGj^Elvp{8o3}cx4NijB~iT@j>}RYQXCQ3p4$ zrhkeR7vx$Y2|ogReMPiTS1X>bw}@8Y7eZvZsxDm&M{ulXo(;^1Uj@)bNNfD0gehkP zVnoG?KpT(SJ|V~n6Z@iyW6ebijQDvN>W**3#;~&~1QEmA900KAaK`30*`QAcef%MvJqcT^GBv^Q)AV2WyOb- zhv+oJB7Tt*g&3$xNQWhH0&IMUq#JwT0I7WIFks-P5UTXXd&RNN!%H_1IuL>N8&l*R zT2lLOf}d}hH1U7U;q~w)9sQIEz4n&xJx7rAip~ z0#qq8l2<7Df)FRfl!k;+RB)B{MQn-62SPz@GR$f^Mpp_HSyf1KbuNs_>>Zc=J#zVh z9a>WAa!nAtl1DF9@qv<1u_9Ad7{||4C9&|Am2Ui{)yE+m>Irjj0uNrVLmGJvN5=#G zr>fh35RLI3d!bBEHxpgxnt z5%?Pvd>*M)!o75oBATG}X}vIa(@DU1y=zb=ma`Bd1yhBE7&<4w5=ahFU=8okh$#&} z5pewQWdIOk(0fqCEmbjn*Mu?T)XSKg>-4I(T9u;9aQ-UJO0>AB%fgynS+EU`qp>)0v z*@8lifRO%SyGY)hYZjE&u(64C-E5uki~ToX7DxWIHY9-HRa_qxE6S-jgc_3|mc+de zSH89WjYeRh8-F5aDG(5Uoe7l;Js@ z<#GgybrXT4@{<}N+^O4_kwif6j?(PZ^Yp6${_$+zoGv%`0Nq^ z-VRlJ!To)1%g;@Sc3YzHCj!mFbb?yXQIRSOuKmA8D~}>=#+;sZr7{>rfUb3L7D*&Y z`QvW{lR?N%M@yRzmeKNI@h#4*IC_vx98)bTYBebrir}9GwXm4E-u?GKefP`5w@)9Q ze*Jp?-NkoT-~H*k$A_Q4e0bE&Yu$YR>F)9I%iU)lI^yHkyI;QO!7q0o6!hZ{|M<&a z?;h{}%HgxsJbeE8>HZ(z{po-FN&I?}AHV$Y z@c!$kyI=3Wy?=bV`}QOv58poi`0~cUsJDQjTEon2s>9;YMVMk4zGDWrn^az7c89Z$FKMI z6r3A`X+@24mjJ(d!=s+S4?lnT{{4rChi?l1{P5xalL-9hLn#I$#Uw&0IZZ=sEmda8 z1*B!^R*<@tZe9L*fgHcUs*q@UxP5)``jzzX>ShulxQrYRE)F#@0DFBSFkjEkr%${Q zMT`a#w^!nC5}7me*+LfhYzpMZr~A+E@4kJ0_?9|Ot%kIAfj@k@`w=Jk#vZ0|KfJzB zF}<19|M}s|8~zsEBF=}9qct}Pl$BlH^Avs+AAZl3Vy4!m)BPk{_M01 z4$TY#c%?VUJn?;X@#b)X>rp;RfA#j_aPm_hM<$^G#8M^VG;wnBWJg*fY62n0#RHMwLRw(ey$@xT83^z=Z5 zTpfdo-Lb6Spl)32(IS@RCal$SEd{Yz`da#b_v*&TImTGNTxAV=O-kyE_A~fis+_%j z-SMqhzEbF0d6E8wI)EekGYgo<;I1xwsy#R0>Z>Ud1_s zx!myq2~IP=7BUVuRnAqWM%hOXasUUi@p<+=E%_z&0HA^3$k>Ui(kypIJT0}W^`o{Y zCx=@>`qFKH1@EKDFHw2ZqmvIlqfgy?h_R@T6?-k-a!00kiU_dWA)&f;L&A`%ReCGk z2P&gCY6M&!Zqp7`^SF`rY^(Z8Z=u3UQa4ircXMJof{g&jilK(hPIE;wTIN! z=!4Tj_Fng!;vU7QkRKDf#kfgawbZnJqh`caxQs(;y%zeZ-KnSt%H8FGs4p~vA^%qP z+@n(TP?h-FY{A94by(u}?fzZSvS(6>UyJV5l{lFApu?%jL-+9N6)|^mzXx?`sV zSFePMLZoVX$NV)R@umE?l#aFXC-_{Cp6l{PjydeS)?$T*b+AEBpNUI4fLB+qRLLs% z{4CsDq3e=t!C}z;)Iqqs63mrW;Cpwh!QK?6_Qb{2H457+d}rj;B3B}#fMcC>d4qsA z%AH&}dfCh1BD~?_5+~SGsoI5``B(qGYyoP7gVBlV!{3~Mg;D()H}&?MPE?@kcos~O zO35TGbF&IP4t2Lx`Zpby$m-|>Z$~B5?YMfUE|(nnOI2(dZz_#cusKb_G5~Q!+w3>k zQ||KWjWX^0n*LGkS-Q6>DAB1+>9*YrwHl;>rda9w>W$QMX2mw^RYq}Rkec^p(7jeG zrYEu$y$VZ%=jttP{a8`1Kv&mAgMjWKT>|=SYpB-M-PkQc{Gr!Of^6-aIo=^_pKBp!;c!K zd-(GGy&lyH`yhwqJk-3qxL>OtIoj~aEKIFdH=DrMrn+5>6IPa+u!=SG1?EFZsq z{M-Fw>beZ_|1O!AYZ)_7t#tKTt*VXT18ZITlis!yR}`M^f15l*vzxTmS5*k^$Tim0 ztdJr`z%Es>g&k(yR!lu0GC!5)8+SNpD!Ij;x$=1a$PSqXOv7x1z55K6v|6aj&nsxV zuRS0$1v$cyfyz@1XTT@fNr)TG>Pz+t1?Hs0VtcecTn{XRg0|jKUDYaKt9Z_zLqilwFR(SB(?ltu7vex8LRk2a{5!%tj8QNKm)>-hrrE2wr z{%v3Jv|R2$hbiiCDNfhxB2sMgQNkPiUei-Q#=EClwV=vptk9|LQrR(A*tehE+DK)i z`KBQ1ZY`H#LixLkzx?Im@$Vl#{`=o9E-x?cK76?UaPjnT@%7vN-|xRXUHr%853F|i z_~plKzOw!L`R7khAGPMD3dn3C!u@oA_vz^;<{=06(zk6rVk4USdpXa18dRei$j!3L zI%YbD!A^0_T$NUNYrey$>2NhdQ0DTVKK^j`{nPu$pTDYc|KaX4iRISo{^|0`88=KdyaG^|4;*Ql0rF`jXN zXH$xTR_C_l6{Hfzm!Hl5l_1-vd=Y>8RXvd*v!j#3V;;Pq!XEQz*LG2MZ768$L&y9E z30m!L?wy|Ya+}F5K}-G``EI z9qv=?mX8g@vRU+W|LOkggV#>C%9Iv#Z5o|fnOO$O!L;-_&7go3Essk#Yc>)_hI^0Y zbp~G85HL%Xv_jB)Byr!Khsc)v1R`0Z+(;hd&M?)XlXOLRn|>9m

3P+Ho)QV@p^j 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 literal 0 HcmV?d00001 diff --git a/Quake/r_alias.cpp b/Quake/r_alias.cpp new file mode 100644 index 0000000..334884f --- /dev/null +++ b/Quake/r_alias.cpp @@ -0,0 +1,1006 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +//r_alias.c -- alias model rendering + +#include "quakedef.hpp" + +extern cvar_t r_drawflat, gl_overbright_models, gl_fullbrights, r_lerpmodels, r_lerpmove; //johnfitz +extern cvar_t scr_fov, cl_gun_fovscale; + +//up to 16 color translated skins +gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz -- changed to an array of pointers + +const float r_avertexnormals[NUMVERTEXNORMALS][3] = { +#include "anorms.hpp" +}; + +extern vec3_t lightcolor; //johnfitz -- replaces "float shadelight" for lit support + +// precalculated dot products for quantized angles +#define SHADEDOT_QUANT 16 +static const float r_avertexnormal_dots[SHADEDOT_QUANT][256] = { +#include "anorm_dots.hpp" +}; + +extern vec3_t lightspot; + +static const float *shadedots = r_avertexnormal_dots[0]; +static vec3_t shadevector; + +static float entalpha; //johnfitz + +static qboolean overbright; //johnfitz + +static qboolean shading = true; //johnfitz -- if false, disable vertex shading for various reasons (fullbright, r_lightmap, showtris, etc) + +//johnfitz -- struct for passing lerp information to drawing functions +typedef struct { + short pose1; + short pose2; + float blend; + vec3_t origin; + vec3_t angles; +} lerpdata_t; +//johnfitz + +static GLuint r_alias_program; + +// uniforms used in vert shader +static GLint blendLoc; +static GLint shadevectorLoc; +static GLint lightColorLoc; + +// uniforms used in frag shader +static GLint texLoc; +static GLint fullbrightTexLoc; +static GLint useFullbrightTexLoc; +static GLint useOverbrightLoc; +static GLint useAlphaTestLoc; + +#define pose1VertexAttrIndex 0 +#define pose1NormalAttrIndex 1 +#define pose2VertexAttrIndex 2 +#define pose2NormalAttrIndex 3 +#define texCoordsAttrIndex 4 + +/* +============= +GLARB_GetXYZOffset + +Returns the offset of the first vertex's meshxyz_t.xyz in the vbo for the given +model and pose. +============= +*/ +static void *GLARB_GetXYZOffset (aliashdr_t *hdr, int pose) +{ + const int xyzoffs = offsetof (meshxyz_t, xyz); + return (void *) (currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + xyzoffs); +} + +/* +============= +GLARB_GetNormalOffset + +Returns the offset of the first vertex's meshxyz_t.normal in the vbo for the +given model and pose. +============= +*/ +static void *GLARB_GetNormalOffset (aliashdr_t *hdr, int pose) +{ + const int normaloffs = offsetof (meshxyz_t, normal); + return (void *)(currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + normaloffs); +} + +/* +============= +GLAlias_CreateShaders +============= +*/ +void GLAlias_CreateShaders (void) +{ + const glsl_attrib_binding_t bindings[] = { + { "TexCoords", texCoordsAttrIndex }, + { "Pose1Vert", pose1VertexAttrIndex }, + { "Pose1Normal", pose1NormalAttrIndex }, + { "Pose2Vert", pose2VertexAttrIndex }, + { "Pose2Normal", pose2NormalAttrIndex } + }; + + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "uniform float Blend;\n" + "uniform vec3 ShadeVector;\n" + "uniform vec4 LightColor;\n" + "attribute vec4 TexCoords; // only xy are used \n" + "attribute vec4 Pose1Vert;\n" + "attribute vec3 Pose1Normal;\n" + "attribute vec4 Pose2Vert;\n" + "attribute vec3 Pose2Normal;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "float r_avertexnormal_dot(vec3 vertexnormal) // from MH \n" + "{\n" + " float dot = dot(vertexnormal, ShadeVector);\n" + " // wtf - this reproduces anorm_dots within as reasonable a degree of tolerance as the >= 0 case\n" + " if (dot < 0.0)\n" + " return 1.0 + dot * (13.0 / 44.0);\n" + " else\n" + " return 1.0 + dot;\n" + "}\n" + "void main()\n" + "{\n" + " gl_TexCoord[0] = TexCoords;\n" + " vec4 lerpedVert = mix(vec4(Pose1Vert.xyz, 1.0), vec4(Pose2Vert.xyz, 1.0), Blend);\n" + " gl_Position = gl_ModelViewProjectionMatrix * lerpedVert;\n" + " FogFragCoord = gl_Position.w;\n" + " float dot1 = r_avertexnormal_dot(Pose1Normal);\n" + " float dot2 = r_avertexnormal_dot(Pose2Normal);\n" + " gl_FrontColor = LightColor * vec4(vec3(mix(dot1, dot2, Blend)), 1.0);\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D Tex;\n" + "uniform sampler2D FullbrightTex;\n" + "uniform bool UseFullbrightTex;\n" + "uniform bool UseOverbright;\n" + "uniform bool UseAlphaTest;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "void main()\n" + "{\n" + " vec4 result = texture2D(Tex, gl_TexCoord[0].xy);\n" + " if (UseAlphaTest && (result.a < 0.666))\n" + " discard;\n" + " result *= gl_Color;\n" + " if (UseOverbright)\n" + " result.rgb *= 2.0;\n" + " if (UseFullbrightTex)\n" + " result += texture2D(FullbrightTex, gl_TexCoord[0].xy);\n" + " result = clamp(result, 0.0, 1.0);\n" + " float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n" + " fog = clamp(fog, 0.0, 1.0);\n" + " result = mix(gl_Fog.color, result, fog);\n" + " result.a = gl_Color.a;\n" // FIXME: This will make almost transparent things cut holes though heavy fog + " gl_FragColor = result;\n" + "}\n"; + + if (!gl_glsl_alias_able) + return; + + r_alias_program = GL_CreateProgram (vertSource, fragSource, Q_COUNTOF(bindings), bindings); + + if (r_alias_program != 0) + { + // get uniform locations + blendLoc = GL_GetUniformLocation (&r_alias_program, "Blend"); + shadevectorLoc = GL_GetUniformLocation (&r_alias_program, "ShadeVector"); + lightColorLoc = GL_GetUniformLocation (&r_alias_program, "LightColor"); + texLoc = GL_GetUniformLocation (&r_alias_program, "Tex"); + fullbrightTexLoc = GL_GetUniformLocation (&r_alias_program, "FullbrightTex"); + useFullbrightTexLoc = GL_GetUniformLocation (&r_alias_program, "UseFullbrightTex"); + useOverbrightLoc = GL_GetUniformLocation (&r_alias_program, "UseOverbright"); + useAlphaTestLoc = GL_GetUniformLocation (&r_alias_program, "UseAlphaTest"); + } +} + +/* +============= +GL_DrawAliasFrame_GLSL -- ericw + +Optimized alias model drawing codepath. +Compared to the original GL_DrawAliasFrame, this makes 1 draw call, +no vertex data is uploaded (it's already in the r_meshvbo and r_meshindexesvbo +static VBOs), and lerping and lighting is done in the vertex shader. + +Supports optional overbright, optional fullbright pixels. + +Based on code by MH from RMQEngine +============= +*/ +void GL_DrawAliasFrame_GLSL (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltexture_t *tx, gltexture_t *fb) +{ + float blend; + + if (lerpdata.pose1 != lerpdata.pose2) + { + blend = lerpdata.blend; + } + else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled + { + blend = 0; + } + + GL_UseProgramFunc (r_alias_program); + + GL_BindBuffer (GL_ARRAY_BUFFER, currententity->model->meshvbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, currententity->model->meshindexesvbo); + + GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc (pose1VertexAttrIndex); + GL_EnableVertexAttribArrayFunc (pose2VertexAttrIndex); + GL_EnableVertexAttribArrayFunc (pose1NormalAttrIndex); + GL_EnableVertexAttribArrayFunc (pose2NormalAttrIndex); + + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, 0, (void *)(intptr_t)currententity->model->vbostofs); + GL_VertexAttribPointerFunc (pose1VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2)); +// GL_TRUE to normalize the signed bytes to [-1 .. 1] + GL_VertexAttribPointerFunc (pose1NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose2)); + +// set uniforms + GL_Uniform1fFunc (blendLoc, blend); + GL_Uniform3fFunc (shadevectorLoc, shadevector[0], shadevector[1], shadevector[2]); + GL_Uniform4fFunc (lightColorLoc, lightcolor[0], lightcolor[1], lightcolor[2], entalpha); + GL_Uniform1iFunc (texLoc, 0); + GL_Uniform1iFunc (fullbrightTexLoc, 1); + GL_Uniform1iFunc (useFullbrightTexLoc, (fb != NULL) ? 1 : 0); + GL_Uniform1fFunc (useOverbrightLoc, overbright); + GL_Uniform1iFunc (useAlphaTestLoc, (currententity->model->flags & MF_HOLEY) ? 1 : 0); + +// set textures + GL_SelectTexture (GL_TEXTURE0); + GL_Bind (tx); + + if (fb) + { + GL_SelectTexture (GL_TEXTURE1); + GL_Bind (fb); + } + +// draw + glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (void *)(intptr_t)currententity->model->vboindexofs); + +// clean up + GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc (pose1VertexAttrIndex); + GL_DisableVertexAttribArrayFunc (pose2VertexAttrIndex); + GL_DisableVertexAttribArrayFunc (pose1NormalAttrIndex); + GL_DisableVertexAttribArrayFunc (pose2NormalAttrIndex); + + GL_UseProgramFunc (0); + GL_SelectTexture (GL_TEXTURE0); + + rs_aliaspasses += paliashdr->numtris; +} + +/* +============= +GL_DrawAliasFrame -- johnfitz -- rewritten to support colored light, lerping, entalpha, multitexture, and r_drawflat +============= +*/ +void GL_DrawAliasFrame (aliashdr_t *paliashdr, lerpdata_t lerpdata) +{ + float vertcolor[4]; + trivertx_t *verts1, *verts2; + int *commands; + int count; + float u,v; + float blend, iblend; + qboolean lerping; + + if (lerpdata.pose1 != lerpdata.pose2) + { + lerping = true; + verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); + verts2 = verts1; + verts1 += lerpdata.pose1 * paliashdr->poseverts; + verts2 += lerpdata.pose2 * paliashdr->poseverts; + blend = lerpdata.blend; + iblend = 1.0f - blend; + } + else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled + { + lerping = false; + verts1 = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata); + verts2 = verts1; // avoid bogus compiler warning + verts1 += lerpdata.pose1 * paliashdr->poseverts; + blend = iblend = 0; // avoid bogus compiler warning + } + + commands = (int *)((byte *)paliashdr + paliashdr->commands); + + vertcolor[3] = entalpha; //never changes, so there's no need to put this inside the loop + + while (1) + { + // get the vertex count and primitive type + count = *commands++; + if (!count) + break; // done + + if (count < 0) + { + count = -count; + glBegin (GL_TRIANGLE_FAN); + } + else + glBegin (GL_TRIANGLE_STRIP); + + do + { + u = ((float *)commands)[0]; + v = ((float *)commands)[1]; + if (mtexenabled) + { + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, u, v); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, u, v); + } + else + glTexCoord2f (u, v); + + commands += 2; + + if (shading) + { + if (r_drawflat_cheatsafe) + { + srand(count * (unsigned int)(src_offset_t)commands); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + } + else if (lerping) + { + vertcolor[0] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[0]; + vertcolor[1] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[1]; + vertcolor[2] = (shadedots[verts1->lightnormalindex]*iblend + shadedots[verts2->lightnormalindex]*blend) * lightcolor[2]; + glColor4fv (vertcolor); + } + else + { + vertcolor[0] = shadedots[verts1->lightnormalindex] * lightcolor[0]; + vertcolor[1] = shadedots[verts1->lightnormalindex] * lightcolor[1]; + vertcolor[2] = shadedots[verts1->lightnormalindex] * lightcolor[2]; + glColor4fv (vertcolor); + } + } + + if (lerping) + { + glVertex3f (verts1->v[0]*iblend + verts2->v[0]*blend, + verts1->v[1]*iblend + verts2->v[1]*blend, + verts1->v[2]*iblend + verts2->v[2]*blend); + verts1++; + verts2++; + } + else + { + glVertex3f (verts1->v[0], verts1->v[1], verts1->v[2]); + verts1++; + } + } while (--count); + + glEnd (); + } + + rs_aliaspasses += paliashdr->numtris; +} + +/* +================= +R_SetupAliasFrame -- johnfitz -- rewritten to support lerping +================= +*/ +void R_SetupAliasFrame (aliashdr_t *paliashdr, int frame, lerpdata_t *lerpdata) +{ + entity_t *e = currententity; + int posenum, numposes; + + if ((frame >= paliashdr->numframes) || (frame < 0)) + { + Con_DPrintf ("R_AliasSetupFrame: no such frame %d for '%s'\n", frame, e->model->name); + frame = 0; + } + + posenum = paliashdr->frames[frame].firstpose; + numposes = paliashdr->frames[frame].numposes; + + if (numposes > 1) + { + e->lerptime = paliashdr->frames[frame].interval; + posenum += (int)(cl.time / e->lerptime) % numposes; + } + else + e->lerptime = 0.1; + + if (e->lerpflags & LERP_RESETANIM) //kill any lerp in progress + { + e->lerpstart = 0; + e->previouspose = posenum; + e->currentpose = posenum; + e->lerpflags -= LERP_RESETANIM; + } + else if (e->currentpose != posenum) // pose changed, start new lerp + { + if (e->lerpflags & LERP_RESETANIM2) //defer lerping one more time + { + e->lerpstart = 0; + e->previouspose = posenum; + e->currentpose = posenum; + e->lerpflags -= LERP_RESETANIM2; + } + else + { + e->lerpstart = cl.time; + e->previouspose = e->currentpose; + e->currentpose = posenum; + } + } + + //set up values + if (r_lerpmodels.value && !(e->model->flags & MOD_NOLERP && r_lerpmodels.value != 2)) + { + if (e->lerpflags & LERP_FINISH && numposes == 1) + lerpdata->blend = CLAMP (0.0f, (float)(cl.time - e->lerpstart) / (e->lerpfinish - e->lerpstart), 1.0f); + else + lerpdata->blend = CLAMP (0.0f, (float)(cl.time - e->lerpstart) / e->lerptime, 1.0f); + if (lerpdata->blend == 1.0f) + e->previouspose = e->currentpose; + lerpdata->pose1 = e->previouspose; + lerpdata->pose2 = e->currentpose; + } + else //don't lerp + { + lerpdata->blend = 1; + lerpdata->pose1 = posenum; + lerpdata->pose2 = posenum; + } +} + +/* +================= +R_SetupEntityTransform -- johnfitz -- set up transform part of lerpdata +================= +*/ +void R_SetupEntityTransform (entity_t *e, lerpdata_t *lerpdata) +{ + float blend; + vec3_t d; + int i; + + // if LERP_RESETMOVE, kill any lerps in progress + if (e->lerpflags & LERP_RESETMOVE) + { + e->movelerpstart = 0; + VectorCopy (e->origin, e->previousorigin); + VectorCopy (e->origin, e->currentorigin); + VectorCopy (e->angles, e->previousangles); + VectorCopy (e->angles, e->currentangles); + e->lerpflags -= LERP_RESETMOVE; + } + else if (!VectorCompare (e->origin, e->currentorigin) || !VectorCompare (e->angles, e->currentangles)) // origin/angles changed, start new lerp + { + e->movelerpstart = cl.time; + VectorCopy (e->currentorigin, e->previousorigin); + VectorCopy (e->origin, e->currentorigin); + VectorCopy (e->currentangles, e->previousangles); + VectorCopy (e->angles, e->currentangles); + } + + //set up values + if (r_lerpmove.value && e != &cl.viewent && e->lerpflags & LERP_MOVESTEP) + { + if (e->lerpflags & LERP_FINISH) + blend = CLAMP (0.0f, (float)(cl.time - e->movelerpstart) / (e->lerpfinish - e->movelerpstart), 1.0f); + else + blend = CLAMP (0.0f, (float)(cl.time - e->movelerpstart) / 0.1f, 1.0f); + + //translation + VectorSubtract (e->currentorigin, e->previousorigin, d); + lerpdata->origin[0] = e->previousorigin[0] + d[0] * blend; + lerpdata->origin[1] = e->previousorigin[1] + d[1] * blend; + lerpdata->origin[2] = e->previousorigin[2] + d[2] * blend; + + //rotation + VectorSubtract (e->currentangles, e->previousangles, d); + for (i = 0; i < 3; i++) + { + if (d[i] > 180) d[i] -= 360; + if (d[i] < -180) d[i] += 360; + } + lerpdata->angles[0] = e->previousangles[0] + d[0] * blend; + lerpdata->angles[1] = e->previousangles[1] + d[1] * blend; + lerpdata->angles[2] = e->previousangles[2] + d[2] * blend; + } + else //don't lerp + { + VectorCopy (e->origin, lerpdata->origin); + VectorCopy (e->angles, lerpdata->angles); + } +} + +/* +================= +R_SetupAliasLighting -- johnfitz -- broken out from R_DrawAliasModel and rewritten +================= +*/ +void R_SetupAliasLighting (entity_t *e) +{ + vec3_t dist; + float add; + int i; + int quantizedangle; + float radiansangle; + + // if the initial trace is completely black, try again from above + // this helps with models whose origin is slightly below ground level + // (e.g. some of the candles in the DOTM start map) + if (!R_LightPoint (e->origin)) + { + vec3_t lpos; + VectorCopy (e->origin, lpos); + lpos[2] += e->model->maxs[2] * 0.5f; + R_LightPoint (lpos); + } + + //add dlights + for (i=0 ; i= cl.time) + { + VectorSubtract (currententity->origin, cl_dlights[i].origin, dist); + add = cl_dlights[i].radius - VectorLength(dist); + if (add > 0) + VectorMA (lightcolor, add, cl_dlights[i].color, lightcolor); + } + } + + // minimum light value on gun (24) + if (e == &cl.viewent) + { + add = 72.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]); + if (add > 0.0f) + { + lightcolor[0] += add / 3.0f; + lightcolor[1] += add / 3.0f; + lightcolor[2] += add / 3.0f; + } + } + + // minimum light value on players (8) + if (currententity > cl_entities && currententity <= cl_entities + cl.maxclients) + { + add = 24.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]); + if (add > 0.0f) + { + lightcolor[0] += add / 3.0f; + lightcolor[1] += add / 3.0f; + lightcolor[2] += add / 3.0f; + } + } + + // clamp lighting so it doesn't overbright as much (96) + if (overbright) + { + add = 288.0f / (lightcolor[0] + lightcolor[1] + lightcolor[2]); + if (add < 1.0f) + VectorScale(lightcolor, add, lightcolor); + } + + //hack up the brightness when fullbrights but no overbrights (256) + if (gl_fullbrights.value && !gl_overbright_models.value) + if (e->model->flags & MOD_FBRIGHTHACK) + { + lightcolor[0] = 256.0f; + lightcolor[1] = 256.0f; + lightcolor[2] = 256.0f; + } + + quantizedangle = ((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1); + +//ericw -- shadevector is passed to the shader to compute shadedots inside the +//shader, see GLAlias_CreateShaders() + radiansangle = (quantizedangle / 16.0) * 2.0 * 3.14159; + shadevector[0] = cos(-radiansangle); + shadevector[1] = sin(-radiansangle); + shadevector[2] = 1; + VectorNormalize(shadevector); +//ericw -- + + shadedots = r_avertexnormal_dots[quantizedangle]; + VectorScale (lightcolor, 1.0f / 200.0f, lightcolor); +} + +/* +================= +R_DrawAliasModel -- johnfitz -- almost completely rewritten +================= +*/ +void R_DrawAliasModel (entity_t *e) +{ + aliashdr_t *paliashdr; + int anim, skinnum; + gltexture_t *tx, *fb; + lerpdata_t lerpdata; + qboolean alphatest = !!(e->model->flags & MF_HOLEY); + float fovscale = 1.0f; + + // + // setup pose/lerp data -- do it first so we don't miss updates due to culling + // + paliashdr = (aliashdr_t *)Mod_Extradata (e->model); + R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); + R_SetupEntityTransform (e, &lerpdata); + + // + // cull it + // + if (R_CullModelForEntity(e)) + return; + + // + // transform it + // + if (e == &cl.viewent && scr_fov.value > 90.f && cl_gun_fovscale.value) + fovscale = tan(scr_fov.value * (0.5f * M_PI / 180.f)); + + glPushMatrix (); + R_RotateForEntity (lerpdata.origin, lerpdata.angles, e->scale); + glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1] * fovscale, paliashdr->scale_origin[2] * fovscale); + glScalef (paliashdr->scale[0], paliashdr->scale[1] * fovscale, paliashdr->scale[2] * fovscale); + + // + // random stuff + // + if (gl_smoothmodels.value && !r_drawflat_cheatsafe) + glShadeModel (GL_SMOOTH); + if (gl_affinemodels.value) + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + overbright = !!gl_overbright_models.value; + shading = true; + + // + // set up for alpha blending + // + if (r_drawflat_cheatsafe || r_lightmap_cheatsafe) //no alpha in drawflat or lightmap mode + entalpha = 1; + else + entalpha = ENTALPHA_DECODE(e->alpha); + if (entalpha == 0) + goto cleanup; + if (entalpha < 1) + { + if (!gl_texture_env_combine) overbright = false; //overbright can't be done in a single pass without combiners + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + } + else if (alphatest) + glEnable (GL_ALPHA_TEST); + + // + // set up lighting + // + rs_aliaspolys += paliashdr->numtris; + R_SetupAliasLighting (e); + + // + // set up textures + // + GL_DisableMultitexture(); + anim = (int)(cl.time*10) & 3; + skinnum = e->skinnum; + if ((skinnum >= paliashdr->numskins) || (skinnum < 0)) + { + Con_DPrintf ("R_DrawAliasModel: no such skin # %d for '%s'\n", skinnum, e->model->name); + // ericw -- display skin 0 for winquake compatibility + skinnum = 0; + } + tx = paliashdr->gltextures[skinnum][anim]; + fb = paliashdr->fbtextures[skinnum][anim]; + if (e->colormap != vid.colormap && !gl_nocolors.value) + { + if ((uintptr_t)e >= (uintptr_t)&cl_entities[1] && (uintptr_t)e <= (uintptr_t)&cl_entities[cl.maxclients]) /* && !strcmp (currententity->model->name, "progs/player.mdl") */ + tx = playertextures[e - cl_entities - 1]; + } + if (!gl_fullbrights.value) + fb = NULL; + + // + // draw it + // + if (r_drawflat_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + srand((int) (cl.time * 1000)); //restore randomness + } + else if (r_fullbright_cheatsafe) + { + GL_Bind (tx); + shading = false; + glColor4f(1,1,1,entalpha); + GL_DrawAliasFrame (paliashdr, lerpdata); + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + } + } + else if (r_lightmap_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + shading = false; + glColor3f(1,1,1); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + } +// call fast path if possible. if the shader compliation failed for some reason, +// r_alias_program will be 0. + else if (r_alias_program != 0) + { + GL_DrawAliasFrame_GLSL (paliashdr, lerpdata, tx, fb); + } + else if (overbright) + { + if (gl_texture_env_combine && gl_mtexable && gl_texture_env_add && fb) //case 1: everything in one pass + { + GL_Bind (tx); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (fb); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + glEnable(GL_BLEND); + GL_DrawAliasFrame (paliashdr, lerpdata); + glDisable(GL_BLEND); + GL_DisableMultitexture(); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (gl_texture_env_combine) //case 2: overbright in one pass, then fullbright pass + { + // first pass + GL_Bind(tx); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_DrawAliasFrame (paliashdr, lerpdata); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + // second pass + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + } + else //case 3: overbright in two passes, then fullbright pass + { + // first pass + GL_Bind(tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DrawAliasFrame (paliashdr, lerpdata); + // second pass -- additive with black fog, to double the object colors but not the fog color + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + // third pass + if (fb) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + } + } + else + { + if (gl_mtexable && gl_texture_env_add && fb) //case 4: fullbright mask using multitexture + { + GL_DisableMultitexture(); // selects TEXTURE0 + GL_Bind (tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_EnableMultitexture(); // selects TEXTURE1 + GL_Bind (fb); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + glEnable(GL_BLEND); + GL_DrawAliasFrame (paliashdr, lerpdata); + glDisable(GL_BLEND); + GL_DisableMultitexture(); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else //case 5: fullbright mask without multitexture + { + // first pass + GL_Bind(tx); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DrawAliasFrame (paliashdr, lerpdata); + // second pass + if (fb) + { + GL_Bind(fb); + glEnable(GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glDepthMask(GL_FALSE); + shading = false; + glColor3f(entalpha,entalpha,entalpha); + Fog_StartAdditive (); + GL_DrawAliasFrame (paliashdr, lerpdata); + Fog_StopAdditive (); + glDepthMask(GL_TRUE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + } + } + } + +cleanup: + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + glShadeModel (GL_FLAT); + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + if (alphatest) + glDisable (GL_ALPHA_TEST); + glColor3f(1,1,1); + glPopMatrix (); +} + +//johnfitz -- values for shadow matrix +#define SHADOW_SKEW_X -0.7 //skew along x axis. -0.7 to mimic glquake shadows +#define SHADOW_SKEW_Y 0 //skew along y axis. 0 to mimic glquake shadows +#define SHADOW_VSCALE 0 //0=completely flat +#define SHADOW_HEIGHT 0.1 //how far above the floor to render the shadow +//johnfitz + +/* +============= +GL_DrawAliasShadow -- johnfitz -- rewritten + +TODO: orient shadow onto "lightplane" (a global mplane_t*) +============= +*/ +void GL_DrawAliasShadow (entity_t *e) +{ + float shadowmatrix[16] = {1, 0, 0, 0, + 0, 1, 0, 0, + SHADOW_SKEW_X, SHADOW_SKEW_Y, SHADOW_VSCALE, 0, + 0, 0, SHADOW_HEIGHT, 1}; + float lheight; + aliashdr_t *paliashdr; + lerpdata_t lerpdata; + + if (R_CullModelForEntity(e)) + return; + + if (e == &cl.viewent || e->model->flags & MOD_NOSHADOW) + return; + + entalpha = ENTALPHA_DECODE(e->alpha); + if (entalpha == 0) return; + + paliashdr = (aliashdr_t *)Mod_Extradata (e->model); + R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); + R_SetupEntityTransform (e, &lerpdata); + R_LightPoint (e->origin); + lheight = currententity->origin[2] - lightspot[2]; + +// set up matrix + glPushMatrix (); + glTranslatef (lerpdata.origin[0], lerpdata.origin[1], lerpdata.origin[2]); + glTranslatef (0,0,-lheight); + glMultMatrixf (shadowmatrix); + glTranslatef (0,0,lheight); + glRotatef (lerpdata.angles[1], 0, 0, 1); + glRotatef (-lerpdata.angles[0], 0, 1, 0); + glRotatef (lerpdata.angles[2], 1, 0, 0); + glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]); + glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]); + +// draw it + glDepthMask(GL_FALSE); + glEnable (GL_BLEND); + GL_DisableMultitexture (); + glDisable (GL_TEXTURE_2D); + shading = false; + glColor4f(0,0,0,entalpha * 0.5); + GL_DrawAliasFrame (paliashdr, lerpdata); + glEnable (GL_TEXTURE_2D); + glDisable (GL_BLEND); + glDepthMask(GL_TRUE); + +//clean up + glPopMatrix (); +} + +/* +================= +R_DrawAliasModel_ShowTris -- johnfitz +================= +*/ +void R_DrawAliasModel_ShowTris (entity_t *e) +{ + aliashdr_t *paliashdr; + lerpdata_t lerpdata; + float fovscale = 1.0f; + + if (R_CullModelForEntity(e)) + return; + + paliashdr = (aliashdr_t *)Mod_Extradata (e->model); + R_SetupAliasFrame (paliashdr, e->frame, &lerpdata); + R_SetupEntityTransform (e, &lerpdata); + + if (e == &cl.viewent && scr_fov.value > 90.f && cl_gun_fovscale.value) + fovscale = tan(scr_fov.value * (0.5f * M_PI / 180.f)); + + glPushMatrix (); + R_RotateForEntity (lerpdata.origin,lerpdata.angles, e->scale); + glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1] * fovscale, paliashdr->scale_origin[2] * fovscale); + glScalef (paliashdr->scale[0], paliashdr->scale[1] * fovscale, paliashdr->scale[2] * fovscale); + + shading = false; + glColor3f(1,1,1); + GL_DrawAliasFrame (paliashdr, lerpdata); + + glPopMatrix (); +} diff --git a/Quake/r_brush.cpp b/Quake/r_brush.cpp new file mode 100644 index 0000000..aafb4cc --- /dev/null +++ b/Quake/r_brush.cpp @@ -0,0 +1,1028 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_brush.c: brush model rendering. renamed from r_surf.c + +#include "quakedef.hpp" + +extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldwater; //johnfitz +extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix + +int gl_lightmap_format; +int lightmap_bytes; + +#define MAX_SANITY_LIGHTMAPS (1u<<20) +struct lightmap_s *lightmaps; +int lightmap_count; + +static int allocated[LMBLOCK_WIDTH]; +static int last_lightmap_allocated; + +static unsigned blocklights[LMBLOCK_WIDTH*LMBLOCK_HEIGHT*3]; //johnfitz -- was 18*18, added lit support (*3) and loosened surface extents maximum (LMBLOCK_WIDTH*LMBLOCK_HEIGHT) + + +/* +=============== +R_TextureAnimation -- johnfitz -- added "frame" param to eliminate use of "currententity" global + +Returns the proper texture for a given time and base texture +=============== +*/ +texture_t *R_TextureAnimation (texture_t *base, int frame) +{ + int relative; + int count; + + if (frame) + if (base->alternate_anims) + base = base->alternate_anims; + + if (!base->anim_total) + return base; + + relative = (int)(cl.time*10) % base->anim_total; + + count = 0; + while (base->anim_min > relative || base->anim_max <= relative) + { + base = base->anim_next; + if (!base) + Sys_Error ("R_TextureAnimation: broken cycle"); + if (++count > 100) + Sys_Error ("R_TextureAnimation: infinite cycle"); + } + + return base; +} + +/* +================ +DrawGLPoly +================ +*/ +void DrawGLPoly (glpoly_t *p) +{ + float *v; + int i; + + glBegin (GL_POLYGON); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glTexCoord2f (v[3], v[4]); + glVertex3fv (v); + } + glEnd (); +} + +/* +================ +DrawGLTriangleFan -- johnfitz -- like DrawGLPoly but for r_showtris +================ +*/ +void DrawGLTriangleFan (glpoly_t *p) +{ + float *v; + int i; + + glBegin (GL_TRIANGLE_FAN); + v = p->verts[0]; + for (i=0 ; inumverts ; i++, v+= VERTEXSIZE) + { + glVertex3fv (v); + } + glEnd (); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_DrawBrushModel +================= +*/ +void R_DrawBrushModel (entity_t *e) +{ + int i, k; + msurface_t *psurf; + float dot; + mplane_t *pplane; + qmodel_t *clmodel; + + if (R_CullModelForEntity(e)) + return; + + currententity = e; + clmodel = e->model; + + VectorSubtract (r_refdef.vieworg, e->origin, modelorg); + if (e->angles[0] || e->angles[1] || e->angles[2]) + { + vec3_t temp; + vec3_t forward, right, up; + + VectorCopy (modelorg, temp); + AngleVectors (e->angles, forward, right, up); + modelorg[0] = DotProduct (temp, forward); + modelorg[1] = -DotProduct (temp, right); + modelorg[2] = DotProduct (temp, up); + } + + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; + +// calculate dynamic lighting for bmodel if it's not an +// instanced model + if (clmodel->firstmodelsurface != 0 && !gl_flashblend.value) + { + for (k=0 ; knodes + clmodel->hulls[0].firstclipnode); + } + } + + glPushMatrix (); + e->angles[0] = -e->angles[0]; // stupid quake bug + if (gl_zfix.value) + { + e->origin[0] -= DIST_EPSILON; + e->origin[1] -= DIST_EPSILON; + e->origin[2] -= DIST_EPSILON; + } + R_RotateForEntity (e->origin, e->angles, e->scale); + if (gl_zfix.value) + { + e->origin[0] += DIST_EPSILON; + e->origin[1] += DIST_EPSILON; + e->origin[2] += DIST_EPSILON; + } + e->angles[0] = -e->angles[0]; // stupid quake bug + + R_ClearTextureChains (clmodel, chain_model); + for (i=0 ; inummodelsurfaces ; i++, psurf++) + { + pplane = psurf->plane; + dot = DotProduct (modelorg, pplane->normal) - pplane->dist; + if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || + (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + { + R_ChainSurface (psurf, chain_model); + R_RenderDynamicLightmaps(psurf); + rs_brushpolys++; + } + } + + R_DrawTextureChains (clmodel, e, chain_model); + R_DrawTextureChains_Water (clmodel, e, chain_model); + + glPopMatrix (); +} + +/* +================= +R_DrawBrushModel_ShowTris -- johnfitz +================= +*/ +void R_DrawBrushModel_ShowTris (entity_t *e) +{ + int i; + msurface_t *psurf; + float dot; + mplane_t *pplane; + qmodel_t *clmodel; + glpoly_t *p; + + if (R_CullModelForEntity(e)) + return; + + currententity = e; + clmodel = e->model; + + VectorSubtract (r_refdef.vieworg, e->origin, modelorg); + if (e->angles[0] || e->angles[1] || e->angles[2]) + { + vec3_t temp; + vec3_t forward, right, up; + + VectorCopy (modelorg, temp); + AngleVectors (e->angles, forward, right, up); + modelorg[0] = DotProduct (temp, forward); + modelorg[1] = -DotProduct (temp, right); + modelorg[2] = DotProduct (temp, up); + } + + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; + + glPushMatrix (); + e->angles[0] = -e->angles[0]; // stupid quake bug + R_RotateForEntity (e->origin, e->angles, e->scale); + e->angles[0] = -e->angles[0]; // stupid quake bug + + // + // draw it + // + for (i=0 ; inummodelsurfaces ; i++, psurf++) + { + pplane = psurf->plane; + dot = DotProduct (modelorg, pplane->normal) - pplane->dist; + if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || + (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + { + if ((psurf->flags & SURF_DRAWTURB) && r_oldwater.value) + for (p = psurf->polys->next; p; p = p->next) + DrawGLTriangleFan (p); + else + DrawGLTriangleFan (psurf->polys); + } + } + + glPopMatrix (); +} + +/* +============================================================= + + LIGHTMAPS + +============================================================= +*/ + +/* +================ +R_RenderDynamicLightmaps +called during rendering +================ +*/ +void R_RenderDynamicLightmaps (msurface_t *fa) +{ + byte *base; + int maps; + glRect_t *theRect; + int smax, tmax; + + if (fa->flags & SURF_DRAWTILED) //johnfitz -- not a lightmapped surface + return; + + // add to lightmap chain + fa->polys->chain = lightmaps[fa->lightmaptexturenum].polys; + lightmaps[fa->lightmaptexturenum].polys = fa->polys; + + // check for lightmap modification + for (maps=0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++) + if (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps]) + goto dynamic; + + if (fa->dlightframe == r_framecount // dynamic this frame + || fa->cached_dlight) // dynamic previously + { +dynamic: + if (r_dynamic.value) + { + struct lightmap_s *lm = &lightmaps[fa->lightmaptexturenum]; + lm->modified = true; + theRect = &lm->rectchange; + if (fa->light_t < theRect->t) { + if (theRect->h) + theRect->h += theRect->t - fa->light_t; + theRect->t = fa->light_t; + } + if (fa->light_s < theRect->l) { + if (theRect->w) + theRect->w += theRect->l - fa->light_s; + theRect->l = fa->light_s; + } + smax = (fa->extents[0]>>4)+1; + tmax = (fa->extents[1]>>4)+1; + if ((theRect->w + theRect->l) < (fa->light_s + smax)) + theRect->w = (fa->light_s-theRect->l)+smax; + if ((theRect->h + theRect->t) < (fa->light_t + tmax)) + theRect->h = (fa->light_t-theRect->t)+tmax; + base = lm->data; + base += fa->light_t * LMBLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes; + R_BuildLightMap (fa, base, LMBLOCK_WIDTH*lightmap_bytes); + } + } +} + +/* +======================== +AllocBlock -- returns a texture number and the position inside it +======================== +*/ +int AllocBlock (int w, int h, int *x, int *y) +{ + int i, j; + int best, best2; + int texnum; + + // ericw -- rather than searching starting at lightmap 0 every time, + // start at the last lightmap we allocated a surface in. + // This makes AllocBlock much faster on large levels (can shave off 3+ seconds + // of load time on a level with 180 lightmaps), at a cost of not quite packing + // lightmaps as tightly vs. not doing this (uses ~5% more lightmaps) + for (texnum=last_lightmap_allocated ; texnum= best) + break; + if (allocated[i+j] > best2) + best2 = allocated[i+j]; + } + if (j == w) + { // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if (best + h > LMBLOCK_HEIGHT) + continue; + + for (i=0 ; iflags & SURF_DRAWTILED) + { + surf->lightmaptexturenum = -1; + return; + } + + smax = (surf->extents[0]>>4)+1; + tmax = (surf->extents[1]>>4)+1; + + surf->lightmaptexturenum = AllocBlock (smax, tmax, &surf->light_s, &surf->light_t); + base = lightmaps[surf->lightmaptexturenum].data; + base += (surf->light_t * LMBLOCK_WIDTH + surf->light_s) * lightmap_bytes; + R_BuildLightMap (surf, base, LMBLOCK_WIDTH*lightmap_bytes); +} + +/* +================ +BuildSurfaceDisplayList -- called at level load time +================ +*/ +void BuildSurfaceDisplayList (msurface_t *fa) +{ + int i, lindex, lnumverts; + medge_t *pedges, *r_pedge; + float *vec; + float s, t, s0, t0, sdiv, tdiv; + glpoly_t *poly; + +// reconstruct the polygon + pedges = currentmodel->edges; + lnumverts = fa->numedges; + + // + // draw texture + // + poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float)); + poly->next = fa->polys; + fa->polys = poly; + poly->numverts = lnumverts; + + if (fa->flags & SURF_DRAWTURB) + { + // match Mod_PolyForUnlitSurface + s0 = t0 = 0.f; + sdiv = tdiv = 128.f; + } + else + { + s0 = fa->texinfo->vecs[0][3]; + t0 = fa->texinfo->vecs[1][3]; + sdiv = fa->texinfo->texture->width; + tdiv = fa->texinfo->texture->height; + } + + for (i=0 ; isurfedges[fa->firstedge + i]; + + if (lindex > 0) + { + r_pedge = &pedges[lindex]; + vec = r_pcurrentvertbase[r_pedge->v[0]].position; + } + else + { + r_pedge = &pedges[-lindex]; + vec = r_pcurrentvertbase[r_pedge->v[1]].position; + } + s = DotProduct (vec, fa->texinfo->vecs[0]) + s0; + s /= sdiv; + + t = DotProduct (vec, fa->texinfo->vecs[1]) + t0; + t /= tdiv; + + VectorCopy (vec, poly->verts[i]); + poly->verts[i][3] = s; + poly->verts[i][4] = t; + + // Q64 RERELEASE texture shift + if (fa->texinfo->texture->shift > 0) + { + poly->verts[i][3] /= ( 2 * fa->texinfo->texture->shift); + poly->verts[i][4] /= ( 2 * fa->texinfo->texture->shift); + } + + // + // lightmap texture coordinates + // + s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3]; + s -= fa->texturemins[0]; + s += fa->light_s*16; + s += 8; + s /= LMBLOCK_WIDTH*16; //fa->texinfo->texture->width; + + t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3]; + t -= fa->texturemins[1]; + t += fa->light_t*16; + t += 8; + t /= LMBLOCK_HEIGHT*16; //fa->texinfo->texture->height; + + poly->verts[i][5] = s; + poly->verts[i][6] = t; + } + + //johnfitz -- removed gl_keeptjunctions code + + poly->numverts = lnumverts; + + // support r_oldwater 1 on lit water + if (fa->flags & SURF_DRAWTURB) + GL_SubdivideSurface (fa); +} + +/* +================== +GL_BuildLightmaps -- called at level load time + +Builds the lightmap texture +with all the surfaces from all brush models +================== +*/ +void GL_BuildLightmaps (void) +{ + char name[24]; + int i, j; + struct lightmap_s *lm; + qmodel_t *m; + + r_framecount = 1; // no dlightcache + + //Spike -- wipe out all the lightmap data (johnfitz -- the gltexture objects were already freed by Mod_ClearAll) + for (i=0; i < lightmap_count; i++) + free(lightmaps[i].data); + free(lightmaps); + lightmaps = NULL; + last_lightmap_allocated = 0; + lightmap_count = 0; + + gl_lightmap_format = GL_RGBA;//FIXME: hardcoded for now! + + switch (gl_lightmap_format) + { + case GL_RGBA: + lightmap_bytes = 4; + break; + case GL_BGRA: + lightmap_bytes = 4; + break; + default: + Sys_Error ("GL_BuildLightmaps: bad lightmap format"); + } + + for (j=1 ; jname[0] == '*') + continue; + r_pcurrentvertbase = m->vertexes; + currentmodel = m; + for (i=0 ; inumsurfaces ; i++) + { + //johnfitz -- rewritten to use SURF_DRAWTILED instead of the sky/water flags + if (m->surfaces[i].flags & SURF_DRAWTILED) + continue; + GL_CreateSurfaceLightmap (m->surfaces + i); + BuildSurfaceDisplayList (m->surfaces + i); + //johnfitz + } + } + + // + // upload all lightmaps that were filled + // + for (i=0; imodified = false; + lm->rectchange.l = LMBLOCK_WIDTH; + lm->rectchange.t = LMBLOCK_HEIGHT; + lm->rectchange.w = 0; + lm->rectchange.h = 0; + + //johnfitz -- use texture manager + sprintf(name, "lightmap%07i",i); + lm->texture = TexMgr_LoadImage (cl.worldmodel, name, LMBLOCK_WIDTH, LMBLOCK_HEIGHT, + SRC_LIGHTMAP, lm->data, "", (src_offset_t)lm->data, TEXPREF_LINEAR | TEXPREF_NOPICMIP); + //johnfitz + } + + //johnfitz -- warn about exceeding old limits + //GLQuake limit was 64 textures of 128x128. Estimate how many 128x128 textures we would need + //given that we are using lightmap_count of LMBLOCK_WIDTH x LMBLOCK_HEIGHT + i = lightmap_count * ((LMBLOCK_WIDTH / 128) * (LMBLOCK_HEIGHT / 128)); + if (i > 64) + Con_DWarning("%i lightmaps exceeds standard limit of 64.\n",i); + //johnfitz +} + +/* +============================================================= + + VBO support + +============================================================= +*/ + +GLuint gl_bmodel_vbo = 0; + +void GL_DeleteBModelVertexBuffer (void) +{ + if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3)) + return; + + GL_DeleteBuffersFunc (1, &gl_bmodel_vbo); + gl_bmodel_vbo = 0; + + GL_ClearBufferBindings (); +} + +/* +================== +GL_BuildBModelVertexBuffer + +Deletes gl_bmodel_vbo if it already exists, then rebuilds it with all +surfaces from world + all brush models +================== +*/ +void GL_BuildBModelVertexBuffer (void) +{ + unsigned int numverts, varray_bytes, varray_index; + int i, j; + qmodel_t *m; + float *varray; + + if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3)) + return; + +// ask GL for a name for our VBO + GL_DeleteBuffersFunc (1, &gl_bmodel_vbo); + GL_GenBuffersFunc (1, &gl_bmodel_vbo); + +// count all verts in all models + numverts = 0; + for (j=1 ; jname[0] == '*' || m->type != mod_brush) + continue; + + for (i=0 ; inumsurfaces ; i++) + { + numverts += m->surfaces[i].numedges; + } + } + +// build vertex array + varray_bytes = VERTEXSIZE * sizeof(float) * numverts; + varray = (float *) malloc (varray_bytes); + varray_index = 0; + + for (j=1 ; jname[0] == '*' || m->type != mod_brush) + continue; + + for (i=0 ; inumsurfaces ; i++) + { + msurface_t *s = &m->surfaces[i]; + s->vbo_firstvert = varray_index; + memcpy (&varray[VERTEXSIZE * varray_index], s->polys->verts, VERTEXSIZE * sizeof(float) * s->numedges); + varray_index += s->numedges; + } + } + +// upload to GPU + GL_BindBufferFunc (GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BufferDataFunc (GL_ARRAY_BUFFER, varray_bytes, varray, GL_STATIC_DRAW); + free (varray); + +// invalidate the cached bindings + GL_ClearBufferBindings (); +} + +/* +=============== +R_AddDynamicLights +=============== +*/ +void R_AddDynamicLights (msurface_t *surf) +{ + int lnum; + int sd, td; + float dist, rad, minlight; + vec3_t impact, local; + int s, t; + int i; + int smax, tmax; + mtexinfo_t *tex; + //johnfitz -- lit support via lordhavoc + float cred, cgreen, cblue, brightness; + unsigned *bl; + //johnfitz + + smax = (surf->extents[0]>>4)+1; + tmax = (surf->extents[1]>>4)+1; + tex = surf->texinfo; + + for (lnum=0 ; lnumdlightbits[lnum >> 5] & (1U << (lnum & 31)))) + continue; // not lit by this light + + rad = cl_dlights[lnum].radius; + dist = DotProduct (cl_dlights[lnum].origin, surf->plane->normal) - + surf->plane->dist; + rad -= fabs(dist); + minlight = cl_dlights[lnum].minlight; + if (rad < minlight) + continue; + minlight = rad - minlight; + + for (i=0 ; i<3 ; i++) + { + impact[i] = cl_dlights[lnum].origin[i] - + surf->plane->normal[i]*dist; + } + + local[0] = DotProduct (impact, tex->vecs[0]) + tex->vecs[0][3]; + local[1] = DotProduct (impact, tex->vecs[1]) + tex->vecs[1][3]; + + local[0] -= surf->texturemins[0]; + local[1] -= surf->texturemins[1]; + + //johnfitz -- lit support via lordhavoc + bl = blocklights; + cred = cl_dlights[lnum].color[0] * 256.0f; + cgreen = cl_dlights[lnum].color[1] * 256.0f; + cblue = cl_dlights[lnum].color[2] * 256.0f; + //johnfitz + for (t = 0 ; t td) + dist = sd + (td>>1); + else + dist = td + (sd>>1); + if (dist < minlight) + //johnfitz -- lit support via lordhavoc + { + brightness = rad - dist; + bl[0] += (int) (brightness * cred); + bl[1] += (int) (brightness * cgreen); + bl[2] += (int) (brightness * cblue); + } + bl += 3; + //johnfitz + } + } + } +} + + +/* +=============== +R_BuildLightMap -- johnfitz -- revised for lit support via lordhavoc + +Combine and scale multiple lightmaps into the 8.8 format in blocklights +=============== +*/ +void R_BuildLightMap (msurface_t *surf, byte *dest, int stride) +{ + const int overbright = !!gl_overbright.value; + const int wide10bits = !!r_lightmapwide.value; + + int smax, tmax; + unsigned r, g, b; + int i, j, size; + byte *lightmap; + unsigned scale; + int maps; + unsigned *bl; + + surf->cached_dlight = (surf->dlightframe == r_framecount); + + smax = (surf->extents[0]>>4)+1; + tmax = (surf->extents[1]>>4)+1; + size = smax*tmax; + lightmap = surf->samples; + + if (cl.worldmodel->lightdata) + { + // clear to no light + memset (&blocklights[0], 0, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc + + // add all the lightmaps + if (lightmap) + { + for (maps = 0 ; maps < MAXLIGHTMAPS && surf->styles[maps] != 255 ; + maps++) + { + scale = d_lightstylevalue[surf->styles[maps]]; + surf->cached_light[maps] = scale; // 8.8 fraction + //johnfitz -- lit support via lordhavoc + bl = blocklights; + for (i=0 ; idlightframe == r_framecount) + R_AddDynamicLights (surf); + } + else + { + // set to full bright if no light data + memset (&blocklights[0], 255, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc + } + +// bound, invert, and shift +// store: + switch (gl_lightmap_format) + { + case GL_RGBA: + stride -= smax * 4; + bl = blocklights; + for (i=0 ; i> 8; + g = *bl++ >> 8; + b = *bl++ >> 8; + } + else + { + r = *bl++ >> 7; + g = *bl++ >> 7; + b = *bl++ >> 7; + if (wide10bits) { + // artifically clamp to 255 so gl_overbright 0 renders as expected in the wide10bits case + r = (r > 255) ? 255 : r; + g = (g > 255) ? 255 : g; + b = (b > 255) ? 255 : b; + goto loc0; + } + } + if (wide10bits) + { + r = (r > 1023)? 1023 : r; + g = (g > 1023)? 1023 : g; + b = (b > 1023)? 1023 : b; + loc0: + *(unsigned int*)dest = (r<<22) | (g<<12) | (b<<2) | 3; + dest += 4; + } + else + { + *dest++ = (r > 255)? 255 : r; + *dest++ = (g > 255)? 255 : g; + *dest++ = (b > 255)? 255 : b; + *dest++ = 255; + } + } + } + break; + case GL_BGRA: + stride -= smax * 4; + bl = blocklights; + for (i=0 ; i> 8; + g = *bl++ >> 8; + b = *bl++ >> 8; + } + else + { + r = *bl++ >> 7; + g = *bl++ >> 7; + b = *bl++ >> 7; + if (wide10bits) { + // artifically clamp to 255 so gl_overbright 0 renders as expected in the wide10bits case + r = (r > 255) ? 255 : r; + g = (g > 255) ? 255 : g; + b = (b > 255) ? 255 : b; + goto loc1; + } + } + if (wide10bits) + { + r = (r > 1023)? 1023 : r; + g = (g > 1023)? 1023 : g; + b = (b > 1023)? 1023 : b; + loc1: + *(unsigned int*)dest = (b<<22) | (g<<12) | (r<<2) | 3; + dest += 4; + } + else + { + *dest++ = (b > 255)? 255 : b; + *dest++ = (g > 255)? 255 : g; + *dest++ = (r > 255)? 255 : r; + *dest++ = 255; + } + } + } + break; + default: + Sys_Error ("R_BuildLightMap: bad lightmap format"); + } +} + +/* +=============== +R_UploadLightmap -- johnfitz -- uploads the modified lightmap to opengl if necessary + +assumes lightmap texture is already bound +=============== +*/ +static void R_UploadLightmap(int lmap) +{ + const int wide10bits = !!r_lightmapwide.value; + const GLenum type = wide10bits ? + GL_UNSIGNED_INT_10_10_10_2 : GL_UNSIGNED_BYTE; + struct lightmap_s *lm = &lightmaps[lmap]; + + if (!lm->modified) + return; + + lm->modified = false; + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, lm->rectchange.t, LMBLOCK_WIDTH, lm->rectchange.h, gl_lightmap_format, + type, lm->data + lm->rectchange.t*LMBLOCK_WIDTH*lightmap_bytes); + lm->rectchange.l = LMBLOCK_WIDTH; + lm->rectchange.t = LMBLOCK_HEIGHT; + lm->rectchange.h = 0; + lm->rectchange.w = 0; + + rs_dynamiclightmaps++; +} + +void R_UploadLightmaps (void) +{ + int lmap; + + for (lmap = 0; lmap < lightmap_count; lmap++) + { + if (!lightmaps[lmap].modified) + continue; + + GL_Bind (lightmaps[lmap].texture); + R_UploadLightmap(lmap); + } +} + +/* +================ +R_RebuildAllLightmaps -- johnfitz -- called when gl_overbright gets toggled +================ +*/ +void R_RebuildAllLightmaps (void) +{ + const int wide10bits = !!r_lightmapwide.value; + const GLenum type = wide10bits ? + GL_UNSIGNED_INT_10_10_10_2 : GL_UNSIGNED_BYTE; + int i, j; + qmodel_t *mod; + msurface_t *fa; + byte *base; + + if (!cl.worldmodel) // is this the correct test? + return; + + //for each surface in each model, rebuild lightmap with new scale + for (i=1; isurfaces[mod->firstmodelsurface]; + for (j=0; jnummodelsurfaces; j++, fa++) + { + if (fa->flags & SURF_DRAWTILED) + continue; + base = lightmaps[fa->lightmaptexturenum].data; + base += fa->light_t * LMBLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes; + R_BuildLightMap (fa, base, LMBLOCK_WIDTH*lightmap_bytes); + } + } + + //for each lightmap, upload it + for (i=0; i 255 ? 255 : r; + a = sharpness * (255 - r); + a = q_min(a,255); + return a; +} + +/* +=============== +R_InitParticleTextures -- johnfitz -- rewritten +=============== +*/ +void R_InitParticleTextures (void) +{ + int x,y; + static byte particle1_data[64*64*4]; + static byte particle2_data[2*2*4]; + static byte particle3_data[64*64*4]; + byte *dst; + + // particle texture 1 -- circle + dst = particle1_data; + for (x=0 ; x<64 ; x++) + for (y=0 ; y<64 ; y++) + { + *dst++ = 255; + *dst++ = 255; + *dst++ = 255; + *dst++ = R_ParticleTextureLookup(x, y, 8); + } + particletexture1 = TexMgr_LoadImage (NULL, "particle1", 64, 64, SRC_RGBA, particle1_data, "", (src_offset_t)particle1_data, TEXPREF_PERSIST | TEXPREF_ALPHA | TEXPREF_LINEAR); + + // particle texture 2 -- square + dst = particle2_data; + for (x=0 ; x<2 ; x++) + for (y=0 ; y<2 ; y++) + { + *dst++ = 255; + *dst++ = 255; + *dst++ = 255; + *dst++ = x || y ? 0 : 255; + } + particletexture2 = TexMgr_LoadImage (NULL, "particle2", 2, 2, SRC_RGBA, particle2_data, "", (src_offset_t)particle2_data, TEXPREF_PERSIST | TEXPREF_ALPHA | TEXPREF_NEAREST); + + // particle texture 3 -- blob + dst = particle3_data; + for (x=0 ; x<64 ; x++) + for (y=0 ; y<64 ; y++) + { + *dst++ = 255; + *dst++ = 255; + *dst++ = 255; + *dst++ = R_ParticleTextureLookup(x, y, 2); + } + particletexture3 = TexMgr_LoadImage (NULL, "particle3", 64, 64, SRC_RGBA, particle3_data, "", (src_offset_t)particle3_data, TEXPREF_PERSIST | TEXPREF_ALPHA | TEXPREF_LINEAR); + + //set default + particletexture = particletexture1; + texturescalefactor = 1.27; +} + +/* +=============== +R_SetParticleTexture_f -- johnfitz +=============== +*/ +static void R_SetParticleTexture_f (cvar_t *var) +{ + switch ((int)(r_particles.value)) + { + case 1: + particletexture = particletexture1; + texturescalefactor = 1.27; + break; + case 2: + particletexture = particletexture2; + texturescalefactor = 1.0; + break; +// case 3: +// particletexture = particletexture3; +// texturescalefactor = 1.5; +// break; + } +} + +/* +=============== +R_InitParticles +=============== +*/ +void R_InitParticles (void) +{ + int i; + + i = COM_CheckParm ("-particles"); + + if (i && i < com_argc - 1) + { + r_numparticles = atoi(com_argv[i + 1]); + if (r_numparticles < ABSOLUTE_MIN_PARTICLES) + r_numparticles = ABSOLUTE_MIN_PARTICLES; + else if (r_numparticles > ABSOLUTE_MAX_PARTICLES) + r_numparticles = ABSOLUTE_MAX_PARTICLES; + } + else + { + r_numparticles = DEFAULT_NUM_PARTICLES; + } + + particles = (particle_t *) + Hunk_AllocName (r_numparticles * sizeof(particle_t), "particles"); + + Cvar_RegisterVariable (&r_particles); //johnfitz + Cvar_SetCallback (&r_particles, R_SetParticleTexture_f); + Cvar_RegisterVariable (&r_quadparticles); //johnfitz + + R_InitParticleTextures (); //johnfitz +} + +/* +=============== +R_EntityParticles +=============== +*/ +static vec3_t avelocities[NUMVERTEXNORMALS]; +static float beamlength = 16; + +void R_EntityParticles (entity_t *ent) +{ + int i; + particle_t *p; + float angle; + float sp, sy, cp, cy; +// float sr, cr; +// int count; + vec3_t forward; + float dist; + + dist = 64; +// count = 50; + + if (!avelocities[0][0]) + { + for (i = 0; i < NUMVERTEXNORMALS; i++) + { + avelocities[i][0] = (rand() & 255) * 0.01; + avelocities[i][1] = (rand() & 255) * 0.01; + avelocities[i][2] = (rand() & 255) * 0.01; + } + } + + for (i = 0; i < NUMVERTEXNORMALS; i++) + { + angle = cl.time * avelocities[i][0]; + sy = sin(angle); + cy = cos(angle); + angle = cl.time * avelocities[i][1]; + sp = sin(angle); + cp = cos(angle); + angle = cl.time * avelocities[i][2]; + // sr = sin(angle); + // cr = cos(angle); + + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 0.01; + p->color = 0x6f; + p->type = pt_explode; + + p->org[0] = ent->origin[0] + r_avertexnormals[i][0]*dist + forward[0]*beamlength; + p->org[1] = ent->origin[1] + r_avertexnormals[i][1]*dist + forward[1]*beamlength; + p->org[2] = ent->origin[2] + r_avertexnormals[i][2]*dist + forward[2]*beamlength; + } +} + +/* +=============== +R_ClearParticles +=============== +*/ +void R_ClearParticles (void) +{ + int i; + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;inext; + p->next = active_particles; + active_particles = p; + + p->die = 99999; + p->color = (-c)&15; + p->type = pt_static; + VectorCopy (vec3_origin, p->vel); + VectorCopy (org, p->org); + } + + fclose (f); + Con_Printf ("%i points read\n", c); +} + +/* +=============== +R_ParseParticleEffect + +Parse an effect out of the server message +=============== +*/ +void R_ParseParticleEffect (void) +{ + vec3_t org, dir; + int i, count, msgcount, color; + + for (i=0 ; i<3 ; i++) + org[i] = MSG_ReadCoord (cl.protocolflags); + for (i=0 ; i<3 ; i++) + dir[i] = MSG_ReadChar () * (1.0/16); + msgcount = MSG_ReadByte (); + color = MSG_ReadByte (); + + if (msgcount == 255) + count = 1024; + else + count = msgcount; + + R_RunParticleEffect (org, dir, color, count); +} + +/* +=============== +R_ParticleExplosion +=============== +*/ +void R_ParticleExplosion (vec3_t org) +{ + int i, j; + particle_t *p; + + for (i=0 ; i<1024 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 5; + p->color = ramp1[0]; + p->ramp = rand()&3; + if (i & 1) + { + p->type = pt_explode; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + else + { + p->type = pt_explode2; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + } +} + +/* +=============== +R_ParticleExplosion2 +=============== +*/ +void R_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength) +{ + int i, j; + particle_t *p; + int colorMod = 0; + + for (i=0; i<512; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 0.3; + p->color = colorStart + (colorMod % colorLength); + colorMod++; + + p->type = pt_blob; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } +} + +/* +=============== +R_BlobExplosion +=============== +*/ +void R_BlobExplosion (vec3_t org) +{ + int i, j; + particle_t *p; + + for (i=0 ; i<1024 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 1 + (rand()&8)*0.05; + + if (i & 1) + { + p->type = pt_blob; + p->color = 66 + rand()%6; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + else + { + p->type = pt_blob2; + p->color = 150 + rand()%6; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + } +} + +/* +=============== +R_RunParticleEffect +=============== +*/ +void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count) +{ + int i, j; + particle_t *p; + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + if (count == 1024) + { // rocket explosion + p->die = cl.time + 5; + p->color = ramp1[0]; + p->ramp = rand()&3; + if (i & 1) + { + p->type = pt_explode; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + else + { + p->type = pt_explode2; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%512)-256; + } + } + } + else + { + p->die = cl.time + 0.1*(rand()%5); + p->color = (color&~7) + (rand()&7); + p->type = pt_slowgrav; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&15)-8); + p->vel[j] = dir[j]*15;// + (rand()%300)-150; + } + } + } +} + +/* +=============== +R_LavaSplash +=============== +*/ +void R_LavaSplash (vec3_t org) +{ + int i, j, k; + particle_t *p; + float vel; + vec3_t dir; + + for (i=-16 ; i<16 ; i++) + for (j=-16 ; j<16 ; j++) + for (k=0 ; k<1 ; k++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 2 + (rand()&31) * 0.02; + p->color = 224 + (rand()&7); + p->type = pt_slowgrav; + + dir[0] = j*8 + (rand()&7); + dir[1] = i*8 + (rand()&7); + dir[2] = 256; + + p->org[0] = org[0] + dir[0]; + p->org[1] = org[1] + dir[1]; + p->org[2] = org[2] + (rand()&63); + + VectorNormalize (dir); + vel = 50 + (rand()&63); + VectorScale (dir, vel, p->vel); + } +} + +/* +=============== +R_TeleportSplash +=============== +*/ +void R_TeleportSplash (vec3_t org) +{ + int i, j, k; + particle_t *p; + float vel; + vec3_t dir; + + for (i=-16 ; i<16 ; i+=4) + { + for (j=-16 ; j<16 ; j+=4) + { + for (k=-24 ; k<32 ; k+=4) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->die = cl.time + 0.2 + (rand()&7) * 0.02; + p->color = 7 + (rand()&7); + p->type = pt_slowgrav; + + dir[0] = j*8; + dir[1] = i*8; + dir[2] = k*8; + + p->org[0] = org[0] + i + (rand()&3); + p->org[1] = org[1] + j + (rand()&3); + p->org[2] = org[2] + k + (rand()&3); + + VectorNormalize (dir); + vel = 50 + (rand()&63); + VectorScale (dir, vel, p->vel); + } + } + } +} + +/* +=============== +R_RocketTrail + +FIXME -- rename function and use #defined types instead of numbers +=============== +*/ +void R_RocketTrail (vec3_t start, vec3_t end, int type) +{ + vec3_t vec; + float len; + int j; + particle_t *p; + int dec; + static int tracercount; + + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + if (type < 128) + dec = 3; + else + { + dec = 1; + type -= 128; + } + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + VectorCopy (vec3_origin, p->vel); + p->die = cl.time + 2; + + switch (type) + { + case 0: // rocket trail + p->ramp = (rand()&3); + p->color = ramp3[(int)p->ramp]; + p->type = pt_fire; + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + break; + + case 1: // smoke smoke + p->ramp = (rand()&3) + 2; + p->color = ramp3[(int)p->ramp]; + p->type = pt_fire; + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + break; + + case 2: // blood + p->type = pt_grav; + p->color = 67 + (rand()&3); + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + break; + + case 3: + case 5: // tracer + p->die = cl.time + 0.5; + p->type = pt_static; + if (type == 3) + p->color = 52 + ((tracercount&4)<<1); + else + p->color = 230 + ((tracercount&4)<<1); + + tracercount++; + + VectorCopy (start, p->org); + if (tracercount & 1) + { + p->vel[0] = 30*vec[1]; + p->vel[1] = 30*-vec[0]; + } + else + { + p->vel[0] = 30*-vec[1]; + p->vel[1] = 30*vec[0]; + } + break; + + case 4: // slight blood + p->type = pt_grav; + p->color = 67 + (rand()&3); + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()%6)-3); + len -= 3; + break; + + case 6: // voor trail + p->color = 9*16 + 8 + (rand()&3); + p->type = pt_static; + p->die = cl.time + 0.3; + for (j=0 ; j<3 ; j++) + p->org[j] = start[j] + ((rand()&15)-8); + break; + } + + VectorAdd (start, vec, start); + } +} + +/* +=============== +CL_RunParticles -- johnfitz -- all the particle behavior, separated from R_DrawParticles +=============== +*/ +void CL_RunParticles (void) +{ + particle_t *p, *kill; + int i; + float time1, time2, time3, dvel, frametime, grav; + extern cvar_t sv_gravity; + + frametime = cl.time - cl.oldtime; + time3 = frametime * 15; + time2 = frametime * 10; + time1 = frametime * 5; + grav = frametime * sv_gravity.value * 0.05; + dvel = 4*frametime; + + for ( ;; ) + { + kill = active_particles; + if (kill && kill->die < cl.time) + { + active_particles = kill->next; + kill->next = free_particles; + free_particles = kill; + continue; + } + break; + } + + for (p=active_particles ; p ; p=p->next) + { + for ( ;; ) + { + kill = p->next; + if (kill && kill->die < cl.time) + { + p->next = kill->next; + kill->next = free_particles; + free_particles = kill; + continue; + } + break; + } + + p->org[0] += p->vel[0]*frametime; + p->org[1] += p->vel[1]*frametime; + p->org[2] += p->vel[2]*frametime; + + switch (p->type) + { + case pt_static: + break; + case pt_fire: + p->ramp += time1; + if (p->ramp >= 6) + p->die = -1; + else + p->color = ramp3[(int)p->ramp]; + p->vel[2] += grav; + break; + + case pt_explode: + p->ramp += time2; + if (p->ramp >=8) + p->die = -1; + else + p->color = ramp1[(int)p->ramp]; + for (i=0 ; i<3 ; i++) + p->vel[i] += p->vel[i]*dvel; + p->vel[2] -= grav; + break; + + case pt_explode2: + p->ramp += time3; + if (p->ramp >=8) + p->die = -1; + else + p->color = ramp2[(int)p->ramp]; + for (i=0 ; i<3 ; i++) + p->vel[i] -= p->vel[i]*frametime; + p->vel[2] -= grav; + break; + + case pt_blob: + for (i=0 ; i<3 ; i++) + p->vel[i] += p->vel[i]*dvel; + p->vel[2] -= grav; + break; + + case pt_blob2: + for (i=0 ; i<2 ; i++) + p->vel[i] -= p->vel[i]*dvel; + p->vel[2] -= grav; + break; + + case pt_grav: + case pt_slowgrav: + p->vel[2] -= grav; + break; + } + } +} + +/* +=============== +R_DrawParticles -- johnfitz -- moved all non-drawing code to CL_RunParticles +=============== +*/ +void R_DrawParticles (void) +{ + particle_t *p; + float scale; + vec3_t up, right, p_up, p_right, p_upright; //johnfitz -- p_ vectors + GLubyte color[4], *c; //johnfitz -- particle transparency + extern cvar_t r_particles; //johnfitz + //float alpha; //johnfitz -- particle transparency + + if (!r_particles.value) + return; + + //ericw -- avoid empty glBegin(),glEnd() pair below; causes issues on AMD + if (!active_particles) + return; + + VectorScale (vup, 1.5, up); + VectorScale (vright, 1.5, right); + + GL_Bind(particletexture); + glEnable (GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glDepthMask (GL_FALSE); //johnfitz -- fix for particle z-buffer bug + + if (r_quadparticles.value) //johnitz -- quads save fillrate + { + glBegin (GL_QUADS); + for (p=active_particles ; p ; p=p->next) + { + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale /= 2.0; //quad is half the size of triangle + + scale *= texturescalefactor; //johnfitz -- compensate for apparent size of different particle textures + + //johnfitz -- particle transparency and fade out + c = (GLubyte *) &d_8to24table[(int)p->color]; + color[0] = c[0]; + color[1] = c[1]; + color[2] = c[2]; + //alpha = CLAMP(0, p->die + 0.5 - cl.time, 1); + color[3] = 255; //(int)(alpha * 255); + glColor4ubv(color); + //johnfitz + + glTexCoord2f (0,0); + glVertex3fv (p->org); + + glTexCoord2f (0.5,0); + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + glTexCoord2f (0.5,0.5); + VectorMA (p_up, scale, right, p_upright); + glVertex3fv (p_upright); + + glTexCoord2f (0,0.5); + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + } + glEnd (); + } + else //johnitz -- triangles save verts + { + glBegin (GL_TRIANGLES); + for (p=active_particles ; p ; p=p->next) + { + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale *= texturescalefactor; //johnfitz -- compensate for apparent size of different particle textures + + //johnfitz -- particle transparency and fade out + c = (GLubyte *) &d_8to24table[(int)p->color]; + color[0] = c[0]; + color[1] = c[1]; + color[2] = c[2]; + //alpha = CLAMP(0, p->die + 0.5 - cl.time, 1); + color[3] = 255; //(int)(alpha * 255); + glColor4ubv(color); + //johnfitz + + glTexCoord2f (0,0); + glVertex3fv (p->org); + + glTexCoord2f (1,0); + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + glTexCoord2f (0,1); + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + } + glEnd (); + } + + glDepthMask (GL_TRUE); //johnfitz -- fix for particle z-buffer bug + glDisable (GL_BLEND); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f(1,1,1); +} + + +/* +=============== +R_DrawParticles_ShowTris -- johnfitz +=============== +*/ +void R_DrawParticles_ShowTris (void) +{ + particle_t *p; + float scale; + vec3_t up, right, p_up, p_right, p_upright; + extern cvar_t r_particles; + + if (!r_particles.value) + return; + + VectorScale (vup, 1.5, up); + VectorScale (vright, 1.5, right); + + if (r_quadparticles.value) + { + for (p=active_particles ; p ; p=p->next) + { + glBegin (GL_TRIANGLE_FAN); + + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale /= 2.0; //quad is half the size of triangle + + scale *= texturescalefactor; //compensate for apparent size of different particle textures + + glVertex3fv (p->org); + + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + VectorMA (p_up, scale, right, p_upright); + glVertex3fv (p_upright); + + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + + glEnd (); + } + } + else + { + glBegin (GL_TRIANGLES); + for (p=active_particles ; p ; p=p->next) + { + // hack a scale up to keep particles from disapearing + scale = (p->org[0] - r_origin[0]) * vpn[0] + + (p->org[1] - r_origin[1]) * vpn[1] + + (p->org[2] - r_origin[2]) * vpn[2]; + if (scale < 20) + scale = 1 + 0.08; //johnfitz -- added .08 to be consistent + else + scale = 1 + scale * 0.004; + + scale *= texturescalefactor; //compensate for apparent size of different particle textures + + glVertex3fv (p->org); + + VectorMA (p->org, scale, up, p_up); + glVertex3fv (p_up); + + VectorMA (p->org, scale, right, p_right); + glVertex3fv (p_right); + } + glEnd (); + } +} + diff --git a/Quake/r_sprite.cpp b/Quake/r_sprite.cpp new file mode 100644 index 0000000..6cbc13d --- /dev/null +++ b/Quake/r_sprite.cpp @@ -0,0 +1,196 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +//r_sprite.c -- sprite model rendering + +#include "quakedef.hpp" + +/* +================ +R_GetSpriteFrame +================ +*/ +mspriteframe_t *R_GetSpriteFrame (entity_t *currentent) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe; + int i, numframes, frame; + float *pintervals, fullinterval, targettime, time; + + psprite = (msprite_t *) currentent->model->cache.data; + frame = currentent->frame; + + if ((frame >= psprite->numframes) || (frame < 0)) + { + Con_DPrintf ("R_DrawSprite: no such frame %d for '%s'\n", frame, currentent->model->name); + frame = 0; + } + + if (psprite->frames[frame].type == SPR_SINGLE) + { + pspriteframe = psprite->frames[frame].frameptr; + } + else if (psprite->frames[frame].type == SPR_ANGLED) + { + // erysdren - angled sprites code backported from FTEQW + vec3_t axis[3]; + AngleVectors(currententity->angles, axis[0], axis[1], axis[2]); + { + float f = DotProduct(vpn, axis[0]); + float r = DotProduct(vright, axis[0]); + int dir = (atan2(r, f)+1.125*M_PI)*(4/M_PI); + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pspriteframe = pspritegroup->frames[dir&7]; + } + } + else + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + + time = cl.time + currentent->syncbase; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by 0 + targettime = time - ((int)(time / fullinterval)) * fullinterval; + + for (i = 0; i < (numframes-1); i++) + { + if (pintervals[i] > targettime) + break; + } + + pspriteframe = pspritegroup->frames[i]; + } + + return pspriteframe; +} + +/* +================= +R_DrawSpriteModel -- johnfitz -- rewritten: now supports all orientations +================= +*/ +void R_DrawSpriteModel (entity_t *e) +{ + vec3_t point, v_forward, v_right, v_up; + msprite_t *psprite; + mspriteframe_t *frame; + float *s_up, *s_right; + float angle, sr, cr; + float scale = ENTSCALE_DECODE(e->scale); + + frame = R_GetSpriteFrame (e); + psprite = (msprite_t *) currententity->model->cache.data; + + switch(psprite->type) + { + case SPR_VP_PARALLEL_UPRIGHT: //faces view plane, up is towards the heavens + v_up[0] = 0; + v_up[1] = 0; + v_up[2] = 1; + CrossProduct(vpn, v_up, v_right); + VectorNormalizeFast(v_right); + s_up = v_up; + s_right = v_right; + break; + case SPR_FACING_UPRIGHT: //faces camera origin, up is towards the heavens + VectorSubtract(currententity->origin, r_origin, v_forward); + v_forward[2] = 0; + VectorNormalizeFast(v_forward); + v_right[0] = v_forward[1]; + v_right[1] = -v_forward[0]; + v_right[2] = 0; + v_up[0] = 0; + v_up[1] = 0; + v_up[2] = 1; + s_up = v_up; + s_right = v_right; + break; + case SPR_VP_PARALLEL: //faces view plane, up is towards the top of the screen + s_up = vup; + s_right = vright; + break; + case SPR_ORIENTED: //pitch yaw roll are independent of camera + AngleVectors (currententity->angles, v_forward, v_right, v_up); + s_up = v_up; + s_right = v_right; + break; + case SPR_VP_PARALLEL_ORIENTED: //faces view plane, but obeys roll value + angle = currententity->angles[ROLL] * M_PI_DIV_180; + sr = sin(angle); + cr = cos(angle); + v_right[0] = vright[0] * cr + vup[0] * sr; + v_right[1] = vright[1] * cr + vup[1] * sr; + v_right[2] = vright[2] * cr + vup[2] * sr; + v_up[0] = vright[0] * -sr + vup[0] * cr; + v_up[1] = vright[1] * -sr + vup[1] * cr; + v_up[2] = vright[2] * -sr + vup[2] * cr; + s_up = v_up; + s_right = v_right; + break; + default: + return; + } + + //johnfitz: offset decals + if (psprite->type == SPR_ORIENTED) + GL_PolygonOffset (OFFSET_DECAL); + + glColor3f (1,1,1); + + GL_DisableMultitexture(); + + GL_Bind(frame->gltexture); + + glEnable (GL_ALPHA_TEST); + glBegin (GL_TRIANGLE_FAN); //was GL_QUADS, but changed to support r_showtris + + glTexCoord2f (0, frame->tmax); + VectorMA (e->origin, frame->down * scale, s_up, point); + VectorMA (point, frame->left * scale, s_right, point); + glVertex3fv (point); + + glTexCoord2f (0, 0); + VectorMA (e->origin, frame->up * scale, s_up, point); + VectorMA (point, frame->left * scale, s_right, point); + glVertex3fv (point); + + glTexCoord2f (frame->smax, 0); + VectorMA (e->origin, frame->up * scale, s_up, point); + VectorMA (point, frame->right * scale, s_right, point); + glVertex3fv (point); + + glTexCoord2f (frame->smax, frame->tmax); + VectorMA (e->origin, frame->down * scale, s_up, point); + VectorMA (point, frame->right * scale, s_right, point); + glVertex3fv (point); + + glEnd (); + glDisable (GL_ALPHA_TEST); + + //johnfitz: offset decals + if (psprite->type == SPR_ORIENTED) + GL_PolygonOffset (OFFSET_NONE); +} diff --git a/Quake/r_world.cpp b/Quake/r_world.cpp new file mode 100644 index 0000000..5db3bd2 --- /dev/null +++ b/Quake/r_world.cpp @@ -0,0 +1,1329 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// r_world.c: world model rendering + +#include "quakedef.hpp" + +extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldwater, r_oldskyleaf, r_showtris; //johnfitz + +byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel); + +//============================================================================== +// +// SETUP CHAINS +// +//============================================================================== + +/* +================ +R_ClearTextureChains -- ericw + +clears texture chains for all textures used by the given model, and also +clears the lightmap chains +================ +*/ +void R_ClearTextureChains (qmodel_t *mod, texchain_t chain) +{ + int i; + + // set all chains to null + for (i=0 ; inumtextures ; i++) + if (mod->textures[i]) + mod->textures[i]->texturechains[chain] = NULL; + + // clear lightmap chains + for (i=0 ; itexturechain = surf->texinfo->texture->texturechains[chain]; + surf->texinfo->texture->texturechains[chain] = surf; +} + +/* +================ +R_BackFaceCull -- johnfitz -- returns true if the surface is facing away from vieworg +================ +*/ +qboolean R_BackFaceCull (msurface_t *surf) +{ + double dot; + + if (surf->plane->type < 3) + dot = r_refdef.vieworg[surf->plane->type] - surf->plane->dist; + else + dot = DotProduct (r_refdef.vieworg, surf->plane->normal) - surf->plane->dist; + + if ((dot < 0) ^ !!(surf->flags & SURF_PLANEBACK)) + return true; + + return false; +} + +/* +=============== +R_MarkSurfaces -- johnfitz -- mark surfaces based on PVS and rebuild texture chains +=============== +*/ +void R_MarkSurfaces (void) +{ + byte *vis; + mleaf_t *leaf; + msurface_t *surf, **mark; + int i, j; + qboolean nearwaterportal; + + // clear lightmap chains + for (i=0 ; ifirstmarksurface; i < r_viewleaf->nummarksurfaces; i++, mark++) + if ((*mark)->flags & SURF_DRAWTURB) + nearwaterportal = true; + + // choose vis data + if (r_novis.value || r_viewleaf->contents == CONTENTS_SOLID || r_viewleaf->contents == CONTENTS_SKY) + vis = Mod_NoVisPVS (cl.worldmodel); + else if (nearwaterportal) + vis = SV_FatPVS (r_origin, cl.worldmodel); + else + vis = Mod_LeafPVS (r_viewleaf, cl.worldmodel); + + r_visframecount++; + + // set all chains to null + for (i=0 ; inumtextures ; i++) + if (cl.worldmodel->textures[i]) + cl.worldmodel->textures[i]->texturechains[chain_world] = NULL; + + // iterate through leaves, marking surfaces + leaf = &cl.worldmodel->leafs[1]; + for (i=0 ; inumleafs ; i++, leaf++) + { + if (vis[i>>3] & (1<<(i&7))) + { + if (R_CullBox(leaf->minmaxs, leaf->minmaxs + 3)) + continue; + + if (r_oldskyleaf.value || leaf->contents != CONTENTS_SKY) + for (j=0, mark = leaf->firstmarksurface; jnummarksurfaces; j++, mark++) + { + surf = *mark; + if (surf->visframe != r_visframecount) + { + surf->visframe = r_visframecount; + if (!R_CullBox(surf->mins, surf->maxs) && !R_BackFaceCull (surf)) + { + rs_brushpolys++; //count wpolys here + R_ChainSurface(surf, chain_world); + R_RenderDynamicLightmaps(surf); + if (surf->texinfo->texture->warpimage) + surf->texinfo->texture->update_warp = true; + } + } + } + + // add static models + if (leaf->efrags) + R_StoreEfrags (&leaf->efrags); + } + } +} + +//============================================================================== +// +// DRAW CHAINS +// +//============================================================================== + +/* +============= +R_BeginTransparentDrawing -- ericw +============= +*/ +static void R_BeginTransparentDrawing (float entalpha) +{ + if (entalpha < 1.0f) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor4f (1,1,1,entalpha); + } +} + +/* +============= +R_EndTransparentDrawing -- ericw +============= +*/ +static void R_EndTransparentDrawing (float entalpha) +{ + if (entalpha < 1.0f) + { + glDepthMask (GL_TRUE); + glDisable (GL_BLEND); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glColor3f (1, 1, 1); + } +} + +/* +================ +R_DrawTextureChains_ShowTris -- johnfitz +================ +*/ +void R_DrawTextureChains_ShowTris (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + glpoly_t *p; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t) + continue; + + if (r_oldwater.value && t->texturechains[chain] && (t->texturechains[chain]->flags & SURF_DRAWTURB)) + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + for (p = s->polys->next; p; p = p->next) + { + DrawGLTriangleFan (p); + } + } + else + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + DrawGLTriangleFan (s->polys); + } + } + } +} + +/* +================ +R_DrawTextureChains_Drawflat -- johnfitz +================ +*/ +void R_DrawTextureChains_Drawflat (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + glpoly_t *p; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t) + continue; + + if (r_oldwater.value && t->texturechains[chain] && (t->texturechains[chain]->flags & SURF_DRAWTURB)) + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + for (p = s->polys->next; p; p = p->next) + { + srand((unsigned int) (uintptr_t) p); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + DrawGLPoly (p); + rs_brushpasses++; + } + } + else + { + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + srand((unsigned int) (uintptr_t) s->polys); + glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0); + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } + } + glColor3f (1,1,1); + srand ((int) (cl.time * 1000)); +} + +/* +================ +R_DrawTextureChains_Glow -- johnfitz +================ +*/ +void R_DrawTextureChains_Glow (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + gltexture_t *glt; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(glt = R_TextureAnimation(t, ent != NULL ? ent->frame : 0)->fullbright)) + continue; + + bound = false; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind (glt); + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } +} + +//============================================================================== +// +// VBO SUPPORT +// +//============================================================================== + +static int R_NumTriangleIndicesForSurf (msurface_t *s) +{ + return 3 * (s->numedges - 2); +} + +/* +================ +R_TriangleIndicesForSurf + +Writes out the triangle indices needed to draw s as a triangle list. +The number of indices it will write is given by R_NumTriangleIndicesForSurf. +================ +*/ +static void R_TriangleIndicesForSurf (msurface_t *s, unsigned int *dest) +{ + int i; + for (i=2; inumedges; i++) + { + *dest++ = s->vbo_firstvert; + *dest++ = s->vbo_firstvert + i - 1; + *dest++ = s->vbo_firstvert + i; + } +} + +#define MAX_BATCH_SIZE 4096 + +static unsigned int vbo_indices[MAX_BATCH_SIZE]; +static unsigned int num_vbo_indices; + +/* +================ +R_ClearBatch +================ +*/ +static void R_ClearBatch (void) +{ + num_vbo_indices = 0; +} + +/* +================ +R_FlushBatch + +Draw the current batch if non-empty and clears it, ready for more R_BatchSurface calls. +================ +*/ +static void R_FlushBatch (void) +{ + if (num_vbo_indices > 0) + { + glDrawElements (GL_TRIANGLES, num_vbo_indices, GL_UNSIGNED_INT, vbo_indices); + num_vbo_indices = 0; + } +} + +/* +================ +R_BatchSurface + +Add the surface to the current batch, or just draw it immediately if we're not +using VBOs. +================ +*/ +static void R_BatchSurface (msurface_t *s) +{ + int num_surf_indices = R_NumTriangleIndicesForSurf (s); + + if (num_surf_indices <= 0) + { + // Con_DWarning ("bad numedges for surface\n"); + return; + } + + if (num_vbo_indices + num_surf_indices > MAX_BATCH_SIZE) + R_FlushBatch(); + + R_TriangleIndicesForSurf (s, &vbo_indices[num_vbo_indices]); + num_vbo_indices += num_surf_indices; +} + +/* +================ +R_DrawTextureChains_Multitexture -- johnfitz +================ +*/ +void R_DrawTextureChains_Multitexture (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i, j; + msurface_t *s; + texture_t *t; + float *v; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWTILED | SURF_NOTEXTURE)) + continue; + + bound = false; + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture); + + if (t->texturechains[chain]->flags & SURF_DRAWFENCE) + glEnable (GL_ALPHA_TEST); // Flip alpha test back on + + GL_EnableMultitexture(); // selects TEXTURE1 + bound = true; + } + GL_Bind (lightmaps[s->lightmaptexturenum].texture); + glBegin(GL_POLYGON); + v = s->polys->verts[0]; + for (j=0 ; jpolys->numverts ; j++, v+= VERTEXSIZE) + { + GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]); + GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + rs_brushpasses++; + } + GL_DisableMultitexture(); // selects TEXTURE0 + + if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE) + glDisable (GL_ALPHA_TEST); // Flip alpha test back off + } +} + +/* +================ +R_DrawTextureChains_NoTexture -- johnfitz + +draws surfs whose textures were missing from the BSP +================ +*/ +void R_DrawTextureChains_NoTexture (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_NOTEXTURE)) + continue; + + bound = false; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind (t->gltexture); + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } +} + +/* +================ +R_DrawTextureChains_TextureOnly -- johnfitz +================ +*/ +void R_DrawTextureChains_TextureOnly (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + qboolean bound; + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWSKY)) + continue; + + bound = false; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture); + + if (t->texturechains[chain]->flags & SURF_DRAWFENCE) + glEnable (GL_ALPHA_TEST); // Flip alpha test back on + + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + + if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE) + glDisable (GL_ALPHA_TEST); // Flip alpha test back off + } +} + +/* +================ +GL_WaterAlphaForEntitySurface -- ericw + +Returns the water alpha to use for the entity and surface combination. +================ +*/ +float GL_WaterAlphaForEntitySurface (entity_t *ent, msurface_t *s) +{ + float entalpha; + if (ent == NULL || ent->alpha == ENTALPHA_DEFAULT) + entalpha = GL_WaterAlphaForSurface(s); + else + entalpha = ENTALPHA_DECODE(ent->alpha); + return entalpha; +} + +static GLuint r_world_program; +extern GLuint gl_bmodel_vbo; + +// uniforms used in vert shader + +// uniforms used in frag shader +static GLint texLoc; +static GLint LMTexLoc; +static GLint fullbrightTexLoc; +static GLint useFullbrightTexLoc; +static GLint useOverbrightLoc; +static GLint useAlphaTestLoc; +static GLint useLightmapWideLoc; +static GLint useLightmapOnlyLoc; +static GLint alphaLoc; + +#define vertAttrIndex 0 +#define texCoordsAttrIndex 1 +#define LMCoordsAttrIndex 2 + +/* +================ +R_DrawTextureChains_Water -- johnfitz +================ +*/ +void R_DrawTextureChains_Water (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + glpoly_t *p; + qboolean bound; + float entalpha; + int lastlightmap; + qboolean has_lit_water; + qboolean has_unlit_water; + + if (r_drawflat_cheatsafe || r_lightmap_cheatsafe) // ericw -- !r_drawworld_cheatsafe check moved to R_DrawWorld_Water () + return; + + has_lit_water = false; + has_unlit_water = false; + + if (r_oldwater.value) + { + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB)) + continue; + bound = false; + entalpha = 1.0f; + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + entalpha = GL_WaterAlphaForEntitySurface (ent, s); + R_BeginTransparentDrawing (entalpha); + GL_Bind (t->gltexture); + bound = true; + } + for (p = s->polys->next; p; p = p->next) + { + DrawWaterPoly (p); + rs_brushpasses++; + } + } + R_EndTransparentDrawing (entalpha); + } + } + else if (cl.worldmodel->haslitwater && r_litwater.value && r_world_program != 0) + { + const int overbright = !!gl_overbright.value; + const int wide10bits = !!r_lightmapwide.value; + + has_lit_water = true; + + GL_UseProgramFunc (r_world_program); + + // Bind the buffers + GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); + + GL_EnableVertexAttribArrayFunc (vertAttrIndex); + GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex); + + GL_VertexAttribPointerFunc (vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0)); + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 3); + GL_VertexAttribPointerFunc (LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 5); + + // Set uniforms + GL_Uniform1iFunc (texLoc, 0); + GL_Uniform1iFunc (LMTexLoc, 1); + GL_Uniform1iFunc (fullbrightTexLoc, 2); + GL_Uniform1iFunc (useFullbrightTexLoc, 0); + GL_Uniform1iFunc (useOverbrightLoc, overbright); + GL_Uniform1iFunc (useAlphaTestLoc, 0); + GL_Uniform1iFunc (useLightmapWideLoc, wide10bits); + GL_Uniform1iFunc (useLightmapOnlyLoc, 0); + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB)) + continue; + + if (t->texturechains[chain]->texinfo->flags & TEX_SPECIAL) + { + has_unlit_water = true; + continue; + } + + bound = false; + entalpha = 1.0f; + lastlightmap = 0; + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + entalpha = GL_WaterAlphaForEntitySurface (ent, s); + if (entalpha < 1.0f) + { + GL_Uniform1fFunc (alphaLoc, entalpha); + R_BeginTransparentDrawing (entalpha); + } + + GL_SelectTexture (GL_TEXTURE0); + GL_Bind (t->warpimage); + + if (model != cl.worldmodel) + { + // ericw -- this is copied from R_DrawSequentialPoly. + // If the poly is not part of the world we have to + // set this flag + t->update_warp = true; // FIXME: one frame too late! + } + + bound = true; + lastlightmap = s->lightmaptexturenum; + } + + if (s->lightmaptexturenum != lastlightmap) + R_FlushBatch (); + + GL_SelectTexture (GL_TEXTURE1); + GL_Bind (lightmaps[s->lightmaptexturenum].texture); + lastlightmap = s->lightmaptexturenum; + R_BatchSurface (s); + + rs_brushpasses++; + } + R_FlushBatch (); + R_EndTransparentDrawing (entalpha); + } + + // clean up + GL_DisableVertexAttribArrayFunc (vertAttrIndex); + GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex); + GL_UseProgramFunc (0); + GL_SelectTexture (GL_TEXTURE0); + } + else + has_unlit_water = true; + + if (has_unlit_water) + { + // Unlit water + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB)) + continue; + if (has_lit_water && !(t->texturechains[chain]->texinfo->flags & TEX_SPECIAL)) + continue; + bound = false; + entalpha = 1.0f; + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + entalpha = GL_WaterAlphaForEntitySurface (ent, s); + R_BeginTransparentDrawing (entalpha); + GL_Bind (t->warpimage); + + if (model != cl.worldmodel) + { + // ericw -- this is copied from R_DrawSequentialPoly. + // If the poly is not part of the world we have to + // set this flag + t->update_warp = true; // FIXME: one frame too late! + } + + bound = true; + } + DrawGLPoly (s->polys); + rs_brushpasses++; + } + R_EndTransparentDrawing (entalpha); + } + } +} + +/* +================ +R_DrawTextureChains_White -- johnfitz -- draw sky and water as white polys when r_lightmap is 1 +================ +*/ +void R_DrawTextureChains_White (qmodel_t *model, texchain_t chain) +{ + int i; + msurface_t *s; + texture_t *t; + + glDisable (GL_TEXTURE_2D); + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTILED)) + continue; + + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + DrawGLPoly (s->polys); + rs_brushpasses++; + } + } + glEnable (GL_TEXTURE_2D); +} + +/* +================ +R_DrawLightmapChains -- johnfitz -- R_BlendLightmaps stripped down to almost nothing +================ +*/ +void R_DrawLightmapChains (void) +{ + int i, j; + glpoly_t *p; + float *v; + + for (i=0 ; ichain) + { + glBegin (GL_POLYGON); + v = p->verts[0]; + for (j=0 ; jnumverts ; j++, v+= VERTEXSIZE) + { + glTexCoord2f (v[5], v[6]); + glVertex3fv (v); + } + glEnd (); + rs_brushpasses++; + } + } +} + +/* +============= +GLWorld_CreateShaders +============= +*/ +void GLWorld_CreateShaders (void) +{ + const glsl_attrib_binding_t bindings[] = { + { "Vert", vertAttrIndex }, + { "TexCoords", texCoordsAttrIndex }, + { "LMCoords", LMCoordsAttrIndex } + }; + + // Driver bug workarounds: + // - "Intel(R) UHD Graphics 600" version "4.6.0 - Build 26.20.100.7263" + // crashing on glUseProgram with `vec3 Vert` and + // `gl_ModelViewProjectionMatrix * vec4(Vert, 1.0);`. Work around with + // making Vert a vec4. (https://sourceforge.net/p/quakespasm/bugs/39/) + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "attribute vec4 Vert;\n" + "attribute vec2 TexCoords;\n" + "attribute vec2 LMCoords;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "void main()\n" + "{\n" + " gl_TexCoord[0] = vec4(TexCoords, 0.0, 0.0);\n" + " gl_TexCoord[1] = vec4(LMCoords, 0.0, 0.0);\n" + " gl_Position = gl_ModelViewProjectionMatrix * Vert;\n" + " FogFragCoord = gl_Position.w;\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D Tex;\n" + "uniform sampler2D LMTex;\n" + "uniform sampler2D FullbrightTex;\n" + "uniform bool UseFullbrightTex;\n" + "uniform bool UseOverbright;\n" + "uniform bool UseAlphaTest;\n" + "uniform bool UseLightmapWide;\n" + "uniform bool UseLightmapOnly;\n" + "uniform float Alpha;\n" + "\n" + "varying float FogFragCoord;\n" + "\n" + "void main()\n" + "{\n" + " vec4 result = texture2D(Tex, gl_TexCoord[0].xy);\n" + " if (UseLightmapOnly)\n" + " result = vec4(0.5, 0.5, 0.5, 1.0);\n" + " if (UseAlphaTest && (result.a < 0.666))\n" + " discard;\n" + " result *= texture2D(LMTex, gl_TexCoord[1].xy);\n" + " if (UseLightmapWide)\n" + " result.rgb *= 4.0;\n" + " if (UseOverbright)\n" + " result.rgb *= 2.0;\n" + " if (UseFullbrightTex)\n" + " result += texture2D(FullbrightTex, gl_TexCoord[0].xy);\n" + " result = clamp(result, 0.0, 1.0);\n" + " float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n" + " fog = clamp(fog, 0.0, 1.0);\n" + " result = mix(gl_Fog.color, result, fog);\n" + " result.a = Alpha;\n" // FIXME: This will make almost transparent things cut holes though heavy fog + " gl_FragColor = result;\n" + "}\n"; + + if (!gl_glsl_alias_able) + return; + + r_world_program = GL_CreateProgram (vertSource, fragSource, Q_COUNTOF(bindings), bindings); + + if (r_world_program != 0) + { + // get uniform locations + texLoc = GL_GetUniformLocation (&r_world_program, "Tex"); + LMTexLoc = GL_GetUniformLocation (&r_world_program, "LMTex"); + fullbrightTexLoc = GL_GetUniformLocation (&r_world_program, "FullbrightTex"); + useFullbrightTexLoc = GL_GetUniformLocation (&r_world_program, "UseFullbrightTex"); + useOverbrightLoc = GL_GetUniformLocation (&r_world_program, "UseOverbright"); + useAlphaTestLoc = GL_GetUniformLocation (&r_world_program, "UseAlphaTest"); + useLightmapWideLoc = GL_GetUniformLocation (&r_world_program, "UseLightmapWide"); + useLightmapOnlyLoc = GL_GetUniformLocation (&r_world_program, "UseLightmapOnly"); + alphaLoc = GL_GetUniformLocation (&r_world_program, "Alpha"); + } +} + +/* +================ +R_DrawTextureChains_GLSL -- ericw + +Draw lightmapped surfaces with fulbrights in one pass, using VBO. +Requires 3 TMUs, OpenGL 2.0 +================ +*/ +void R_DrawTextureChains_GLSL (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + const float entalpha = (ent != NULL) ? + ENTALPHA_DECODE(ent->alpha) : 1.0f; + const int overbright = !!gl_overbright.value; + const int wide10bits = !!r_lightmapwide.value; + + int i; + msurface_t *s; + texture_t *t; + qboolean bound; + int lastlightmap; + gltexture_t *fullbright = NULL; + +// enable blending / disable depth writes + if (entalpha < 1) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + } + + GL_UseProgramFunc (r_world_program); + +// Bind the buffers + GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); // indices come from client memory! + + GL_EnableVertexAttribArrayFunc (vertAttrIndex); + GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex); + + GL_VertexAttribPointerFunc (vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0)); + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 3); + GL_VertexAttribPointerFunc (LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 5); + +// set uniforms + GL_Uniform1iFunc (texLoc, 0); + GL_Uniform1iFunc (LMTexLoc, 1); + GL_Uniform1iFunc (fullbrightTexLoc, 2); + GL_Uniform1iFunc (useFullbrightTexLoc, 0); + GL_Uniform1iFunc (useOverbrightLoc, overbright); + GL_Uniform1iFunc (useAlphaTestLoc, 0); + GL_Uniform1iFunc (useLightmapWideLoc, wide10bits); + GL_Uniform1iFunc (useLightmapOnlyLoc, 0); + GL_Uniform1fFunc (alphaLoc, entalpha); + + for (i=0 ; inumtextures ; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWTILED | SURF_NOTEXTURE)) + continue; + + // Enable/disable TMU 2 (fullbrights) + // FIXME: Move below to where we bind GL_TEXTURE0 + if (gl_fullbrights.value && (fullbright = R_TextureAnimation(t, ent != NULL ? ent->frame : 0)->fullbright)) + { + GL_SelectTexture (GL_TEXTURE2); + GL_Bind (fullbright); + GL_Uniform1iFunc (useFullbrightTexLoc, 1); + } + else + GL_Uniform1iFunc (useFullbrightTexLoc, 0); + + R_ClearBatch (); + + bound = false; + lastlightmap = 0; // avoid compiler warning + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (!bound) //only bind once we are sure we need this texture + { + GL_SelectTexture (GL_TEXTURE0); + GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture); + if (t->texturechains[chain]->flags & SURF_DRAWFENCE) + GL_Uniform1iFunc (useAlphaTestLoc, 1); // Flip alpha test back on + + bound = true; + lastlightmap = s->lightmaptexturenum; + } + + if (s->lightmaptexturenum != lastlightmap) + R_FlushBatch (); + + GL_SelectTexture (GL_TEXTURE1); + GL_Bind (lightmaps[s->lightmaptexturenum].texture); + lastlightmap = s->lightmaptexturenum; + R_BatchSurface (s); + + rs_brushpasses++; + } + + R_FlushBatch (); + + if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE) + GL_Uniform1iFunc (useAlphaTestLoc, 0); // Flip alpha test back off + } + + // clean up + GL_DisableVertexAttribArrayFunc (vertAttrIndex); + GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex); + + GL_UseProgramFunc (0); + GL_SelectTexture (GL_TEXTURE0); + + if (entalpha < 1) + { + glDepthMask (GL_TRUE); + glDisable (GL_BLEND); + } +} + +/* +================ +R_DrawLightmapChains_GLSL -- ericw +================ +*/ +void R_DrawLightmapChains_GLSL(qmodel_t* model, entity_t* ent, texchain_t chain) +{ + const int overbright = !!gl_overbright.value; + const int wide10bits = !!r_lightmapwide.value; + + int i; + msurface_t* s; + texture_t* t; + int lastlightmap; + + GL_UseProgramFunc(r_world_program); + + // Bind the buffers + GL_BindBuffer(GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // indices come from client memory! + + GL_EnableVertexAttribArrayFunc(vertAttrIndex); + GL_EnableVertexAttribArrayFunc(texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc(LMCoordsAttrIndex); + + GL_VertexAttribPointerFunc(vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float*)0)); + GL_VertexAttribPointerFunc(texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float*)0) + 3); + GL_VertexAttribPointerFunc(LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float*)0) + 5); + + // set uniforms + GL_Uniform1iFunc(texLoc, 0); + GL_Uniform1iFunc(LMTexLoc, 1); + GL_Uniform1iFunc(fullbrightTexLoc, 2); + GL_Uniform1iFunc(useFullbrightTexLoc, 0); + GL_Uniform1iFunc(useOverbrightLoc, overbright); + GL_Uniform1iFunc(useAlphaTestLoc, 0); + GL_Uniform1iFunc(useLightmapWideLoc, wide10bits); + GL_Uniform1fFunc(alphaLoc, 1.0f); + GL_Uniform1iFunc(useFullbrightTexLoc, 0); + GL_Uniform1iFunc(useLightmapOnlyLoc, 1); + + R_ClearBatch(); + lastlightmap = -1; + + for (i = 0; i < model->numtextures; i++) + { + t = model->textures[i]; + + if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTILED | SURF_NOTEXTURE)) + continue; + + if (t->texturechains[chain]->texinfo->flags & TEX_SPECIAL) + continue; // unlit water + + for (s = t->texturechains[chain]; s; s = s->texturechain) + { + if (s->lightmaptexturenum < 0) + continue; + + if (s->lightmaptexturenum != lastlightmap) + { + R_FlushBatch(); + + GL_SelectTexture(GL_TEXTURE1); + GL_Bind(lightmaps[s->lightmaptexturenum].texture); + lastlightmap = s->lightmaptexturenum; + } + R_BatchSurface(s); + + rs_brushpasses++; + } + } + + R_FlushBatch(); + + // clean up + GL_DisableVertexAttribArrayFunc(vertAttrIndex); + GL_DisableVertexAttribArrayFunc(texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc(LMCoordsAttrIndex); + + GL_UseProgramFunc(0); + GL_SelectTexture(GL_TEXTURE0); +} + +/* +============= +R_DrawWorld -- johnfitz -- rewritten +============= +*/ +void R_DrawTextureChains (qmodel_t *model, entity_t *ent, texchain_t chain) +{ + float entalpha; + + if (ent != NULL) + entalpha = ENTALPHA_DECODE(ent->alpha); + else + entalpha = 1; + + R_UploadLightmaps (); + + if (r_drawflat_cheatsafe) + { + glDisable (GL_TEXTURE_2D); + R_DrawTextureChains_Drawflat (model, chain); + glEnable (GL_TEXTURE_2D); + return; + } + + if (r_fullbright_cheatsafe) + { + R_BeginTransparentDrawing (entalpha); + R_DrawTextureChains_TextureOnly (model, ent, chain); + R_EndTransparentDrawing (entalpha); + goto fullbrights; + } + + if (r_lightmap_cheatsafe) + { + if (r_world_program != 0) + { + R_DrawLightmapChains_GLSL(model, ent, chain); + return; + } + + if (!gl_overbright.value) + { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0.5, 0.5, 0.5); + } + R_DrawLightmapChains (); + if (!gl_overbright.value) + { + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + R_DrawTextureChains_White (model, chain); + return; + } + + R_BeginTransparentDrawing (entalpha); + + R_DrawTextureChains_NoTexture (model, chain); + + // OpenGL 2 fast path + if (r_world_program != 0) + { + R_EndTransparentDrawing (entalpha); + + R_DrawTextureChains_GLSL (model, ent, chain); + return; + } + + if (gl_overbright.value) + { + if (gl_texture_env_combine && gl_mtexable) //case 1: texture and lightmap in one pass, overbright using texture combiners + { + GL_EnableMultitexture (); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f); + GL_DisableMultitexture (); + R_DrawTextureChains_Multitexture (model, ent, chain); + GL_EnableMultitexture (); + glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DisableMultitexture (); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (entalpha < 1) //case 2: can't do multipass if entity has alpha, so just draw the texture + { + R_DrawTextureChains_TextureOnly (model, ent, chain); + } + else //case 3: texture in one pass, lightmap in second pass using 2x modulation blend func, fog in third pass + { + //to make fog work with multipass lightmapping, need to do one pass + //with no fog, one modulate pass with black fog, and one additive + //pass with black geometry and normal fog + Fog_DisableGFog (); + R_DrawTextureChains_TextureOnly (model, ent, chain); + Fog_EnableGFog (); + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc (GL_DST_COLOR, GL_SRC_COLOR); //2x modulate + Fog_StartAdditive (); + R_DrawLightmapChains (); + Fog_StopAdditive (); + if (Fog_GetDensity() > 0) + { + glBlendFunc(GL_ONE, GL_ONE); //add + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0,0,0); + R_DrawTextureChains_TextureOnly (model, ent, chain); + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } + } + else + { + if (gl_mtexable) //case 4: texture and lightmap in one pass, regular modulation + { + GL_EnableMultitexture (); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GL_DisableMultitexture (); + R_DrawTextureChains_Multitexture (model, ent, chain); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + else if (entalpha < 1) //case 5: can't do multipass if entity has alpha, so just draw the texture + { + R_DrawTextureChains_TextureOnly (model, ent, chain); + } + else //case 6: texture in one pass, lightmap in a second pass, fog in third pass + { + //to make fog work with multipass lightmapping, need to do one pass + //with no fog, one modulate pass with black fog, and one additive + //pass with black geometry and normal fog + Fog_DisableGFog (); + R_DrawTextureChains_TextureOnly (model, ent, chain); + Fog_EnableGFog (); + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); //modulate + Fog_StartAdditive (); + R_DrawLightmapChains (); + Fog_StopAdditive (); + if (Fog_GetDensity() > 0) + { + glBlendFunc(GL_ONE, GL_ONE); //add + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f(0,0,0); + R_DrawTextureChains_TextureOnly (model, ent, chain); + glColor3f(1,1,1); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + } + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } + } + + R_EndTransparentDrawing (entalpha); + +fullbrights: + if (gl_fullbrights.value) + { + glDepthMask (GL_FALSE); + glEnable (GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glColor3f (entalpha, entalpha, entalpha); + Fog_StartAdditive (); + R_DrawTextureChains_Glow (model, ent, chain); + Fog_StopAdditive (); + glColor3f (1, 1, 1); + glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable (GL_BLEND); + glDepthMask (GL_TRUE); + } +} + +/* +============= +R_DrawWorld -- ericw -- moved from R_DrawTextureChains, which is no longer specific to the world. +============= +*/ +void R_DrawWorld (void) +{ + if (!r_drawworld_cheatsafe) + return; + + R_DrawTextureChains (cl.worldmodel, NULL, chain_world); +} + +/* +============= +R_DrawWorld_Water -- ericw -- moved from R_DrawTextureChains_Water, which is no longer specific to the world. +============= +*/ +void R_DrawWorld_Water (void) +{ + if (!r_drawworld_cheatsafe) + return; + + R_DrawTextureChains_Water (cl.worldmodel, NULL, chain_world); +} + +/* +============= +R_DrawWorld_ShowTris -- ericw -- moved from R_DrawTextureChains_ShowTris, which is no longer specific to the world. +============= +*/ +void R_DrawWorld_ShowTris (void) +{ + if (!r_drawworld_cheatsafe) + return; + + R_DrawTextureChains_ShowTris (cl.worldmodel, chain_world); +} diff --git a/Quake/render.hpp b/Quake/render.hpp new file mode 100644 index 0000000..ef600ee --- /dev/null +++ b/Quake/render.hpp @@ -0,0 +1,176 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_RENDER_H +#define _QUAKE_RENDER_H + +// refresh.h -- public interface to refresh functions + +#define MAXCLIPPLANES 11 + +#define TOP_RANGE 16 // soldier uniform colors +#define BOTTOM_RANGE 96 + +//============================================================================= + +typedef struct efrag_s +{ + struct efrag_s *leafnext; + struct entity_s *entity; +} efrag_t; + +//johnfitz -- for lerping +#define LERP_MOVESTEP (1<<0) //this is a MOVETYPE_STEP entity, enable movement lerp +#define LERP_RESETANIM (1<<1) //disable anim lerping until next anim frame +#define LERP_RESETANIM2 (1<<2) //set this and previous flag to disable anim lerping for two anim frames +#define LERP_RESETMOVE (1<<3) //disable movement lerping until next origin/angles change +#define LERP_FINISH (1<<4) //use lerpfinish time from server update instead of assuming interval of 0.1 +//johnfitz + +typedef struct entity_s +{ + qboolean forcelink; // model changed + + int update_type; + + entity_state_t baseline; // to fill in defaults in updates + + double msgtime; // time of last update + vec3_t msg_origins[2]; // last two updates (0 is newest) + vec3_t origin; + vec3_t msg_angles[2]; // last two updates (0 is newest) + vec3_t angles; + struct qmodel_s *model; // NULL = no model + struct efrag_s *efrag; // linked list of efrags + int frame; + float syncbase; // for client-side animations + byte *colormap; + int effects; // light, particles, etc + int skinnum; // for Alias models + int visframe; // last frame this entity was + // found in an active leaf + + int dlightframe; // dynamic lighting + int dlightbits; + +// FIXME: could turn these into a union + struct mnode_s *topnode; // for bmodels, first world node + // that splits bmodel, or NULL if + // not split + + byte alpha; //johnfitz -- alpha + byte scale; + byte lerpflags; //johnfitz -- lerping + float lerpstart; //johnfitz -- animation lerping + float lerptime; //johnfitz -- animation lerping + float lerpfinish; //johnfitz -- lerping -- server sent us a more accurate interval, use it instead of 0.1 + short previouspose; //johnfitz -- animation lerping + short currentpose; //johnfitz -- animation lerping +// short futurepose; //johnfitz -- animation lerping + float movelerpstart; //johnfitz -- transform lerping + vec3_t previousorigin; //johnfitz -- transform lerping + vec3_t currentorigin; //johnfitz -- transform lerping + vec3_t previousangles; //johnfitz -- transform lerping + vec3_t currentangles; //johnfitz -- transform lerping +} entity_t; + +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + vrect_t vrect; // subwindow in video for refresh + // FIXME: not need vrect next field here? + vrect_t aliasvrect; // scaled Alias version + int vrectright, vrectbottom; // right & bottom screen coords + int aliasvrectright, aliasvrectbottom; // scaled Alias versions + float vrectrightedge; // rightmost right edge we care about, + // for use in edge list + float fvrectx, fvrecty; // for floating-point compares + float fvrectx_adj, fvrecty_adj; // left and top edges, for clamping + int vrect_x_adj_shift20; // (vrect.x + 0.5 - epsilon) << 20 + int vrectright_adj_shift20; // (vrectright + 0.5 - epsilon) << 20 + float fvrectright_adj, fvrectbottom_adj; + // right and bottom edges, for clamping + float fvrectright; // rightmost edge, for Alias clamping + float fvrectbottom; // bottommost edge, for Alias clamping + float horizontalFieldOfView; // at Z = 1.0, this many X is visible + // 2.0 = 90 degrees + float xOrigin; // should probably allways be 0.5 + float yOrigin; // between be around 0.3 to 0.5 + + vec3_t vieworg; + vec3_t viewangles; + + float fov_x, fov_y; + + int ambientlight; +} refdef_t; + + +// +// refresh +// +extern int reinit_surfcache; + + +extern refdef_t r_refdef; +extern vec3_t r_origin, vpn, vright, vup; + + +void R_Init (void); +void R_InitTextures (void); +void R_InitEfrags (void); +void R_RenderView (void); // must set r_refdef first +void R_ViewChanged (vrect_t *pvrect, int lineadj, float aspect); + // called whenever r_refdef or vid change +//void R_InitSky (struct texture_s *mt); // called at level load + +void R_CheckEfrags (void); //johnfitz +void R_AddEfrags (entity_t *ent); + +void R_NewMap (void); + + +void R_ParseParticleEffect (void); +void R_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count); +void R_RocketTrail (vec3_t start, vec3_t end, int type); +void R_EntityParticles (entity_t *ent); +void R_BlobExplosion (vec3_t org); +void R_ParticleExplosion (vec3_t org); +void R_ParticleExplosion2 (vec3_t org, int colorStart, int colorLength); +void R_LavaSplash (vec3_t org); +void R_TeleportSplash (vec3_t org); + +void R_PushDlights (void); + + +// +// surface cache related +// +extern int reinit_surfcache; // if 1, surface cache is currently empty and + +int D_SurfaceCacheForRes (int width, int height); +void D_FlushCaches (void); +void D_DeleteSurfaceCache (void); +void D_InitCaches (void *buffer, int size); +void R_SetVrect (vrect_t *pvrect, vrect_t *pvrectin, int lineadj); + +#endif /* _QUAKE_RENDER_H */ diff --git a/Quake/resource.hpp b/Quake/resource.hpp new file mode 100644 index 0000000..d31f0fa --- /dev/null +++ b/Quake/resource.hpp @@ -0,0 +1,26 @@ +#ifndef _QUAKE_RESOURCE_H +#define _QUAKE_RESOURCE_H + +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by winquake.rc +// +#define IDS_STRING1 1 +#define IDI_ICON2 1 +#define IDD_DIALOG1 108 +#define IDD_PROGRESS 109 +#define IDC_PROGRESS 1000 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 113 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif + +#endif /* _QUAKE_RESOURCE_H */ + diff --git a/Quake/sbar.cpp b/Quake/sbar.cpp new file mode 100644 index 0000000..56805ff --- /dev/null +++ b/Quake/sbar.cpp @@ -0,0 +1,1336 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sbar.c -- status bar code + +#include "quakedef.hpp" + +static int sb_updates; // if >= vid.numpages, no update needed + +#define STAT_MINUS 10 // num frame for '-' stats digit + +static qpic_t *sb_nums[2][11]; +static qpic_t *sb_colon, *sb_slash; +static qpic_t *sb_ibar; +static qpic_t *sb_sbar; +static qpic_t *sb_scorebar; + +static qpic_t *sb_weapons[7][8]; // 0 is active, 1 is owned, 2-5 are flashes +static qpic_t *sb_ammo[4]; +static qpic_t *sb_sigil[4]; +static qpic_t *sb_armor[3]; +static qpic_t *sb_items[32]; + +static qpic_t *sb_faces[7][2]; // 0 is gibbed, 1 is dead, 2-6 are alive + // 0 is static, 1 is temporary animation +static qpic_t *sb_face_invis; +static qpic_t *sb_face_quad; +static qpic_t *sb_face_invuln; +static qpic_t *sb_face_invis_invuln; + +static qboolean sb_showscores; + +int sb_lines; // scan lines to draw + +static qpic_t *rsb_invbar[2]; +static qpic_t *rsb_weapons[5]; +static qpic_t *rsb_items[2]; +static qpic_t *rsb_ammo[3]; +static qpic_t *rsb_teambord; // PGM 01/19/97 - team color border + +//MED 01/04/97 added two more weapons + 3 alternates for grenade launcher +static qpic_t *hsb_weapons[7][5]; // 0 is active, 1 is owned, 2-5 are flashes +//MED 01/04/97 added array to simplify weapon parsing +static int hipweapons[4] = {HIT_LASER_CANNON_BIT,HIT_MJOLNIR_BIT,4,HIT_PROXIMITY_GUN_BIT}; +//MED 01/04/97 added hipnotic items array +static qpic_t *hsb_items[2]; + +void Sbar_MiniDeathmatchOverlay (void); +void Sbar_DeathmatchOverlay (void); +void M_DrawPic (int x, int y, qpic_t *pic); + +/* +=============== +Sbar_ShowScores + +Tab key down +=============== +*/ +void Sbar_ShowScores (void) +{ + if (sb_showscores) + return; + sb_showscores = true; + sb_updates = 0; +} + +/* +=============== +Sbar_DontShowScores + +Tab key up +=============== +*/ +void Sbar_DontShowScores (void) +{ + sb_showscores = false; + sb_updates = 0; +} + +/* +=============== +Sbar_Changed +=============== +*/ +void Sbar_Changed (void) +{ + sb_updates = 0; // update next frame +} + +/* +=============== +Sbar_LoadPics -- johnfitz -- load all the sbar pics +=============== +*/ +void Sbar_LoadPics (void) +{ + int i; + + for (i = 0; i < 10; i++) + { + sb_nums[0][i] = Draw_PicFromWad (va("num_%i",i)); + sb_nums[1][i] = Draw_PicFromWad (va("anum_%i",i)); + } + + sb_nums[0][10] = Draw_PicFromWad ("num_minus"); + sb_nums[1][10] = Draw_PicFromWad ("anum_minus"); + + sb_colon = Draw_PicFromWad ("num_colon"); + sb_slash = Draw_PicFromWad ("num_slash"); + + sb_weapons[0][0] = Draw_PicFromWad ("inv_shotgun"); + sb_weapons[0][1] = Draw_PicFromWad ("inv_sshotgun"); + sb_weapons[0][2] = Draw_PicFromWad ("inv_nailgun"); + sb_weapons[0][3] = Draw_PicFromWad ("inv_snailgun"); + sb_weapons[0][4] = Draw_PicFromWad ("inv_rlaunch"); + sb_weapons[0][5] = Draw_PicFromWad ("inv_srlaunch"); + sb_weapons[0][6] = Draw_PicFromWad ("inv_lightng"); + + sb_weapons[1][0] = Draw_PicFromWad ("inv2_shotgun"); + sb_weapons[1][1] = Draw_PicFromWad ("inv2_sshotgun"); + sb_weapons[1][2] = Draw_PicFromWad ("inv2_nailgun"); + sb_weapons[1][3] = Draw_PicFromWad ("inv2_snailgun"); + sb_weapons[1][4] = Draw_PicFromWad ("inv2_rlaunch"); + sb_weapons[1][5] = Draw_PicFromWad ("inv2_srlaunch"); + sb_weapons[1][6] = Draw_PicFromWad ("inv2_lightng"); + + for (i = 0; i < 5; i++) + { + sb_weapons[2+i][0] = Draw_PicFromWad (va("inva%i_shotgun",i+1)); + sb_weapons[2+i][1] = Draw_PicFromWad (va("inva%i_sshotgun",i+1)); + sb_weapons[2+i][2] = Draw_PicFromWad (va("inva%i_nailgun",i+1)); + sb_weapons[2+i][3] = Draw_PicFromWad (va("inva%i_snailgun",i+1)); + sb_weapons[2+i][4] = Draw_PicFromWad (va("inva%i_rlaunch",i+1)); + sb_weapons[2+i][5] = Draw_PicFromWad (va("inva%i_srlaunch",i+1)); + sb_weapons[2+i][6] = Draw_PicFromWad (va("inva%i_lightng",i+1)); + } + + sb_ammo[0] = Draw_PicFromWad ("sb_shells"); + sb_ammo[1] = Draw_PicFromWad ("sb_nails"); + sb_ammo[2] = Draw_PicFromWad ("sb_rocket"); + sb_ammo[3] = Draw_PicFromWad ("sb_cells"); + + sb_armor[0] = Draw_PicFromWad ("sb_armor1"); + sb_armor[1] = Draw_PicFromWad ("sb_armor2"); + sb_armor[2] = Draw_PicFromWad ("sb_armor3"); + + sb_items[0] = Draw_PicFromWad ("sb_key1"); + sb_items[1] = Draw_PicFromWad ("sb_key2"); + sb_items[2] = Draw_PicFromWad ("sb_invis"); + sb_items[3] = Draw_PicFromWad ("sb_invuln"); + sb_items[4] = Draw_PicFromWad ("sb_suit"); + sb_items[5] = Draw_PicFromWad ("sb_quad"); + + sb_sigil[0] = Draw_PicFromWad ("sb_sigil1"); + sb_sigil[1] = Draw_PicFromWad ("sb_sigil2"); + sb_sigil[2] = Draw_PicFromWad ("sb_sigil3"); + sb_sigil[3] = Draw_PicFromWad ("sb_sigil4"); + + sb_faces[4][0] = Draw_PicFromWad ("face1"); + sb_faces[4][1] = Draw_PicFromWad ("face_p1"); + sb_faces[3][0] = Draw_PicFromWad ("face2"); + sb_faces[3][1] = Draw_PicFromWad ("face_p2"); + sb_faces[2][0] = Draw_PicFromWad ("face3"); + sb_faces[2][1] = Draw_PicFromWad ("face_p3"); + sb_faces[1][0] = Draw_PicFromWad ("face4"); + sb_faces[1][1] = Draw_PicFromWad ("face_p4"); + sb_faces[0][0] = Draw_PicFromWad ("face5"); + sb_faces[0][1] = Draw_PicFromWad ("face_p5"); + + sb_face_invis = Draw_PicFromWad ("face_invis"); + sb_face_invuln = Draw_PicFromWad ("face_invul2"); + sb_face_invis_invuln = Draw_PicFromWad ("face_inv2"); + sb_face_quad = Draw_PicFromWad ("face_quad"); + + sb_sbar = Draw_PicFromWad ("sbar"); + sb_ibar = Draw_PicFromWad ("ibar"); + sb_scorebar = Draw_PicFromWad ("scorebar"); + +//MED 01/04/97 added new hipnotic weapons + if (hipnotic) + { + hsb_weapons[0][0] = Draw_PicFromWad ("inv_laser"); + hsb_weapons[0][1] = Draw_PicFromWad ("inv_mjolnir"); + hsb_weapons[0][2] = Draw_PicFromWad ("inv_gren_prox"); + hsb_weapons[0][3] = Draw_PicFromWad ("inv_prox_gren"); + hsb_weapons[0][4] = Draw_PicFromWad ("inv_prox"); + + hsb_weapons[1][0] = Draw_PicFromWad ("inv2_laser"); + hsb_weapons[1][1] = Draw_PicFromWad ("inv2_mjolnir"); + hsb_weapons[1][2] = Draw_PicFromWad ("inv2_gren_prox"); + hsb_weapons[1][3] = Draw_PicFromWad ("inv2_prox_gren"); + hsb_weapons[1][4] = Draw_PicFromWad ("inv2_prox"); + + for (i = 0; i < 5; i++) + { + hsb_weapons[2+i][0] = Draw_PicFromWad (va("inva%i_laser",i+1)); + hsb_weapons[2+i][1] = Draw_PicFromWad (va("inva%i_mjolnir",i+1)); + hsb_weapons[2+i][2] = Draw_PicFromWad (va("inva%i_gren_prox",i+1)); + hsb_weapons[2+i][3] = Draw_PicFromWad (va("inva%i_prox_gren",i+1)); + hsb_weapons[2+i][4] = Draw_PicFromWad (va("inva%i_prox",i+1)); + } + + hsb_items[0] = Draw_PicFromWad ("sb_wsuit"); + hsb_items[1] = Draw_PicFromWad ("sb_eshld"); + } + + if (rogue) + { + rsb_invbar[0] = Draw_PicFromWad ("r_invbar1"); + rsb_invbar[1] = Draw_PicFromWad ("r_invbar2"); + + rsb_weapons[0] = Draw_PicFromWad ("r_lava"); + rsb_weapons[1] = Draw_PicFromWad ("r_superlava"); + rsb_weapons[2] = Draw_PicFromWad ("r_gren"); + rsb_weapons[3] = Draw_PicFromWad ("r_multirock"); + rsb_weapons[4] = Draw_PicFromWad ("r_plasma"); + + rsb_items[0] = Draw_PicFromWad ("r_shield1"); + rsb_items[1] = Draw_PicFromWad ("r_agrav1"); + +// PGM 01/19/97 - team color border + rsb_teambord = Draw_PicFromWad ("r_teambord"); +// PGM 01/19/97 - team color border + + rsb_ammo[0] = Draw_PicFromWad ("r_ammolava"); + rsb_ammo[1] = Draw_PicFromWad ("r_ammomulti"); + rsb_ammo[2] = Draw_PicFromWad ("r_ammoplasma"); + } +} + +/* +=============== +Sbar_Init -- johnfitz -- rewritten +=============== +*/ +void Sbar_Init (void) +{ + Cmd_AddCommand ("+showscores", Sbar_ShowScores); + Cmd_AddCommand ("-showscores", Sbar_DontShowScores); + + Sbar_LoadPics (); +} + + +//============================================================================= + +// drawing routines are relative to the status bar location + +/* +============= +Sbar_DrawPic -- johnfitz -- rewritten now that GL_SetCanvas is doing the work +============= +*/ +void Sbar_DrawPic (int x, int y, qpic_t *pic) +{ + Draw_Pic (x, y + 24, pic); +} + +/* +============= +Sbar_DrawPicAlpha -- johnfitz +============= +*/ +void Sbar_DrawPicAlpha (int x, int y, qpic_t *pic, float alpha) +{ + glDisable (GL_ALPHA_TEST); + glEnable (GL_BLEND); + glColor4f(1,1,1,alpha); + Draw_Pic (x, y + 24, pic); + glColor4f(1,1,1,1); // ericw -- changed from glColor3f to work around intel 855 bug with "r_oldwater 0" and "scr_sbaralpha 0" + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); +} + +/* +================ +Sbar_DrawCharacter -- johnfitz -- rewritten now that GL_SetCanvas is doing the work +================ +*/ +void Sbar_DrawCharacter (int x, int y, int num) +{ + Draw_Character (x, y + 24, num); +} + +/* +================ +Sbar_DrawString -- johnfitz -- rewritten now that GL_SetCanvas is doing the work +================ +*/ +void Sbar_DrawString (int x, int y, const char *str) +{ + Draw_String (x, y + 24, str); +} + +/* +=============== +Sbar_DrawScrollString -- johnfitz + +scroll the string inside a glscissor region +=============== +*/ +void Sbar_DrawScrollString (int x, int y, int width, const char *str) +{ + float scale; + int len, ofs, left; + + scale = CLAMP (1.0f, scr_sbarscale.value, (float)glwidth / 320.0f); + left = x * scale; + if (cl.gametype != GAME_DEATHMATCH) + left += (((float)glwidth - 320.0 * scale) / 2); + + glEnable (GL_SCISSOR_TEST); + glScissor (left, 0, width * scale, glheight); + + len = strlen(str)*8 + 40; + ofs = ((int)(realtime*30))%len; + Sbar_DrawString (x - ofs, y, str); + Sbar_DrawCharacter (x - ofs + len - 32, y, '/'); + Sbar_DrawCharacter (x - ofs + len - 24, y, '/'); + Sbar_DrawCharacter (x - ofs + len - 16, y, '/'); + Sbar_DrawString (x - ofs + len, y, str); + + glDisable (GL_SCISSOR_TEST); +} + +/* +============= +Sbar_itoa +============= +*/ +int Sbar_itoa (int num, char *buf) +{ + char *str; + int pow10; + int dig; + + str = buf; + + if (num < 0) + { + *str++ = '-'; + num = -num; + } + + for (pow10 = 10 ; num >= pow10 ; pow10 *= 10) + ; + + do + { + pow10 /= 10; + dig = num/pow10; + *str++ = '0'+dig; + num -= dig*pow10; + } while (pow10 != 1); + + *str = 0; + + return str-buf; +} + + +/* +============= +Sbar_DrawNum +============= +*/ +void Sbar_DrawNum (int x, int y, int num, int digits, int color) +{ + char str[12]; + char *ptr; + int l, frame; + + num = q_min(999,num); //johnfitz -- cap high values rather than truncating number + + l = Sbar_itoa (num, str); + ptr = str; + if (l > digits) + ptr += (l-digits); + if (l < digits) + x += (digits-l)*24; + + while (*ptr) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + Sbar_DrawPic (x,y,sb_nums[color][frame]); //johnfitz -- DrawTransPic is obsolete + x += 24; + ptr++; + } +} + +//============================================================================= + +static int fragsort[MAX_SCOREBOARD]; + +static int scoreboardlines; + +/* +=============== +Sbar_SortFrags +=============== +*/ +void Sbar_SortFrags (void) +{ + int i, j, k; + +// sort by frags + scoreboardlines = 0; + for (i = 0; i < cl.maxclients; i++) + { + if (cl.scores[i].name[0]) + { + fragsort[scoreboardlines] = i; + scoreboardlines++; + } + } + + for (i = 0; i < scoreboardlines; i++) + { + for (j = 0; j < scoreboardlines - 1 - i; j++) + { + if (cl.scores[fragsort[j]].frags < cl.scores[fragsort[j+1]].frags) + { + k = fragsort[j]; + fragsort[j] = fragsort[j+1]; + fragsort[j+1] = k; + } + } + } +} + +int Sbar_ColorForMap (int m) +{ + return m < 128 ? m + 8 : m + 8; +} + +/* +=============== +Sbar_SoloScoreboard -- johnfitz -- new layout +=============== +*/ +void Sbar_SoloScoreboard (void) +{ + char str[256]; + int minutes, seconds, tens, units; + int left, right, len; + + sprintf (str,"Kills: %i/%i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + left = 8 + strlen (str) * 8; + Sbar_DrawString (8, 12, str); + + sprintf (str,"Secrets: %i/%i", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]); + right = 312 - strlen (str) * 8; + Sbar_DrawString (right, 12, str); + + if (!fitzmode) + { /* QuakeSpasm customization: */ + q_snprintf (str, sizeof(str), "skill %i", (int)(skill.value + 0.5)); + Sbar_DrawString ((left + right) / 2 - strlen (str) * 4, 12, str); + + q_snprintf (str, sizeof(str), "%s (%s)", cl.levelname, cl.mapname); + len = strlen (str); + if (len > 40) + Sbar_DrawScrollString (0, 4, 320, str); + else + Sbar_DrawString (160 - len*4, 4, str); + return; + } + minutes = cl.time / 60; + seconds = cl.time - 60*minutes; + tens = seconds / 10; + units = seconds - 10*tens; + sprintf (str,"%i:%i%i", minutes, tens, units); + Sbar_DrawString (160 - strlen(str)*4, 12, str); + + len = strlen (cl.levelname); + if (len > 40) + Sbar_DrawScrollString (0, 4, 320, cl.levelname); + else + Sbar_DrawString (160 - len*4, 4, cl.levelname); +} + +/* +=============== +Sbar_DrawScoreboard +=============== +*/ +void Sbar_DrawScoreboard (void) +{ + Sbar_SoloScoreboard (); + if (cl.gametype == GAME_DEATHMATCH) + Sbar_DeathmatchOverlay (); +} + +//============================================================================= + +/* +=============== +Sbar_DrawInventory +=============== +*/ +void Sbar_DrawInventory (void) +{ + int i, val; + char num[6]; + float time; + int flashon; + + if (rogue) + { + if ( cl.stats[STAT_ACTIVEWEAPON] >= RIT_LAVA_NAILGUN ) + Sbar_DrawPicAlpha (0, -24, rsb_invbar[0], scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + else + Sbar_DrawPicAlpha (0, -24, rsb_invbar[1], scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + } + else + { + Sbar_DrawPicAlpha (0, -24, sb_ibar, scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + } + +// weapons + for (i = 0; i < 7; i++) + { + if (cl.items & (IT_SHOTGUN<= 10) + { + if ( cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN< 1) + sb_updates = 0; // force update to remove flash + } + } + +// MED 01/04/97 +// hipnotic weapons + if (hipnotic) + { + int grenadeflashing = 0; + for (i = 0; i < 4; i++) + { + if (cl.items & (1<= 10) + { + if (cl.stats[STAT_ACTIVEWEAPON] == (1< 1) + sb_updates = 0; // force update to remove flash + } + } + } + + if (rogue) + { + // check for powered up weapon. + if ( cl.stats[STAT_ACTIVEWEAPON] >= RIT_LAVA_NAILGUN ) + { + for (i=0;i<5;i++) + { + if (cl.stats[STAT_ACTIVEWEAPON] == (RIT_LAVA_NAILGUN << i)) + { + Sbar_DrawPic ((i+2)*24, -16, rsb_weapons[i]); + } + } + } + } + +// ammo counts + for (i = 0; i < 4; i++) + { + val = cl.stats[STAT_SHELLS+i]; + val = (val < 0)? 0 : q_min(999,val);//johnfitz -- cap displayed value to 999 + sprintf (num, "%3i", val); + if (num[0] != ' ') + Sbar_DrawCharacter ( (6*i+1)*8 + 2, -24, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter ( (6*i+2)*8 + 2, -24, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter ( (6*i+3)*8 + 2, -24, 18 + num[2] - '0'); + } + + flashon = 0; + // items + for (i = 0; i < 6; i++) + { + if (cl.items & (1<<(17+i))) + { + time = cl.item_gettime[17+i]; + if (time && time > cl.time - 2 && flashon) + { // flash frame + sb_updates = 0; + } + else + { + //MED 01/04/97 changed keys + if (!hipnotic || (i > 1)) + { + Sbar_DrawPic (192 + i*16, -16, sb_items[i]); + } + } + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + //MED 01/04/97 added hipnotic items + // hipnotic items + if (hipnotic) + { + for (i = 0; i < 2; i++) + { + if (cl.items & (1<<(24+i))) + { + time = cl.item_gettime[24+i]; + if (time && time > cl.time - 2 && flashon ) + { // flash frame + sb_updates = 0; + } + else + { + Sbar_DrawPic (288 + i*16, -16, hsb_items[i]); + } + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + } + + if (rogue) + { + // new rogue items + for (i = 0; i < 2; i++) + { + if (cl.items & (1<<(29+i))) + { + time = cl.item_gettime[29+i]; + if (time && time > cl.time - 2 && flashon) + { // flash frame + sb_updates = 0; + } + else + { + Sbar_DrawPic (288 + i*16, -16, rsb_items[i]); + } + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + } + else + { + // sigils + for (i = 0; i < 4; i++) + { + if (cl.items & (1<<(28+i))) + { + time = cl.item_gettime[28+i]; + if (time && time > cl.time - 2 && flashon) + { // flash frame + sb_updates = 0; + } + else + Sbar_DrawPic (320-32 + i*8, -16, sb_sigil[i]); + if (time && time > cl.time - 2) + sb_updates = 0; + } + } + } +} + +//============================================================================= + +/* +=============== +Sbar_DrawFrags -- johnfitz -- heavy revision +=============== +*/ +void Sbar_DrawFrags (void) +{ + int numscores, i, x, color; + char num[12]; + scoreboard_t *s; + + Sbar_SortFrags (); + +// draw the text + numscores = q_min(scoreboardlines, 4); + + for (i = 0, x = 184; iname[0]) + continue; + + // top color + color = s->colors & 0xf0; + color = Sbar_ColorForMap (color); + Draw_Fill (x + 10, 1, 28, 4, color, 1); + + // bottom color + color = (s->colors & 15)<<4; + color = Sbar_ColorForMap (color); + Draw_Fill (x + 10, 5, 28, 3, color, 1); + + // number + sprintf (num, "%3i", s->frags); + Sbar_DrawCharacter (x + 12, -24, num[0]); + Sbar_DrawCharacter (x + 20, -24, num[1]); + Sbar_DrawCharacter (x + 28, -24, num[2]); + + // brackets + if (fragsort[i] == cl.viewentity - 1) + { + Sbar_DrawCharacter (x + 6, -24, 16); + Sbar_DrawCharacter (x + 32, -24, 17); + } + } +} + +//============================================================================= + + +/* +=============== +Sbar_DrawFace +=============== +*/ +void Sbar_DrawFace (void) +{ + int f, anim; + +// PGM 01/19/97 - team color drawing +// PGM 03/02/97 - fixed so color swatch only appears in CTF modes + if (rogue && (cl.maxclients != 1) && (teamplay.value>3) && (teamplay.value<7)) + { + int top, bottom; + int xofs; + char num[12]; + scoreboard_t *s; + + s = &cl.scores[cl.viewentity - 1]; + // draw background + top = s->colors & 0xf0; + bottom = (s->colors & 15)<<4; + top = Sbar_ColorForMap (top); + bottom = Sbar_ColorForMap (bottom); + + if (cl.gametype == GAME_DEATHMATCH) + xofs = 113; + else + xofs = ((vid.width - 320)>>1) + 113; + + Sbar_DrawPic (112, 0, rsb_teambord); + Draw_Fill (xofs, /*vid.height-*/24+3, 22, 9, top, 1); //johnfitz -- sbar coords are now relative + Draw_Fill (xofs, /*vid.height-*/24+12, 22, 9, bottom, 1); //johnfitz -- sbar coords are now relative + + // draw number + f = s->frags; + sprintf (num, "%3i",f); + + if (top == 8) + { + if (num[0] != ' ') + Sbar_DrawCharacter(113, 3, 18 + num[0] - '0'); + if (num[1] != ' ') + Sbar_DrawCharacter(120, 3, 18 + num[1] - '0'); + if (num[2] != ' ') + Sbar_DrawCharacter(127, 3, 18 + num[2] - '0'); + } + else + { + Sbar_DrawCharacter (113, 3, num[0]); + Sbar_DrawCharacter (120, 3, num[1]); + Sbar_DrawCharacter (127, 3, num[2]); + } + + return; + } +// PGM 01/19/97 - team color drawing + + if ((cl.items & (IT_INVISIBILITY | IT_INVULNERABILITY)) + == (IT_INVISIBILITY | IT_INVULNERABILITY)) + { + Sbar_DrawPic (112, 0, sb_face_invis_invuln); + return; + } + if (cl.items & IT_QUAD) + { + Sbar_DrawPic (112, 0, sb_face_quad ); + return; + } + if (cl.items & IT_INVISIBILITY) + { + Sbar_DrawPic (112, 0, sb_face_invis ); + return; + } + if (cl.items & IT_INVULNERABILITY) + { + Sbar_DrawPic (112, 0, sb_face_invuln); + return; + } + + if (cl.stats[STAT_HEALTH] >= 100) + f = 4; + else + f = cl.stats[STAT_HEALTH] / 20; + if (f < 0) // in case we ever decide to draw when health <= 0 + f = 0; + + if (cl.time <= cl.faceanimtime) + { + anim = 1; + sb_updates = 0; // make sure the anim gets drawn over + } + else + anim = 0; + Sbar_DrawPic (112, 0, sb_faces[f][anim]); +} + +/* +=============== +Sbar_Draw +=============== +*/ +void Sbar_Draw (void) +{ + float w; //johnfitz + + if (scr_con_current == vid.height) + return; // console is full screen + + if (cl.intermission) + return; //johnfitz -- never draw sbar during intermission + + if (sb_updates >= vid.numpages && !gl_clear.value && scr_sbaralpha.value >= 1 //johnfitz -- gl_clear, scr_sbaralpha + && !(gl_glsl_gamma_able && vid_gamma.value != 1)) //ericw -- must draw sbar every frame if doing glsl gamma + return; + + sb_updates++; + + GL_SetCanvas (CANVAS_DEFAULT); //johnfitz + + //johnfitz -- don't waste fillrate by clearing the area behind the sbar + w = CLAMP (320.0f, scr_sbarscale.value * 320.0f, (float)glwidth); + if (sb_lines && glwidth > w) + { + if (scr_sbaralpha.value < 1) + Draw_TileClear (0, glheight - sb_lines, glwidth, sb_lines); + if (cl.gametype == GAME_DEATHMATCH) + Draw_TileClear (w, glheight - sb_lines, glwidth - w, sb_lines); + else + { + Draw_TileClear (0, glheight - sb_lines, (glwidth - w) / 2.0f, sb_lines); + Draw_TileClear ((glwidth - w) / 2.0f + w, glheight - sb_lines, (glwidth - w) / 2.0f, sb_lines); + } + } + //johnfitz + + GL_SetCanvas (CANVAS_SBAR); //johnfitz + + if (scr_viewsize.value < 110) //johnfitz -- check viewsize instead of sb_lines + { + Sbar_DrawInventory (); + if (cl.maxclients != 1) + Sbar_DrawFrags (); + } + + if (sb_showscores || cl.stats[STAT_HEALTH] <= 0) + { + Sbar_DrawPicAlpha (0, 0, sb_scorebar, scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + Sbar_DrawScoreboard (); + sb_updates = 0; + } + else if (scr_viewsize.value < 120) //johnfitz -- check viewsize instead of sb_lines + { + Sbar_DrawPicAlpha (0, 0, sb_sbar, scr_sbaralpha.value); //johnfitz -- scr_sbaralpha + + // keys (hipnotic only) + //MED 01/04/97 moved keys here so they would not be overwritten + if (hipnotic) + { + if (cl.items & IT_KEY1) + Sbar_DrawPic (209, 3, sb_items[0]); + if (cl.items & IT_KEY2) + Sbar_DrawPic (209, 12, sb_items[1]); + } + // armor + if (cl.items & IT_INVULNERABILITY) + { + Sbar_DrawNum (24, 0, 666, 3, 1); + Sbar_DrawPic (0, 0, draw_disc); + } + else + { + if (rogue) + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, + cl.stats[STAT_ARMOR] <= 25); + if (cl.items & RIT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.items & RIT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.items & RIT_ARMOR1) + Sbar_DrawPic (0, 0, sb_armor[0]); + } + else + { + Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3 + , cl.stats[STAT_ARMOR] <= 25); + if (cl.items & IT_ARMOR3) + Sbar_DrawPic (0, 0, sb_armor[2]); + else if (cl.items & IT_ARMOR2) + Sbar_DrawPic (0, 0, sb_armor[1]); + else if (cl.items & IT_ARMOR1) + Sbar_DrawPic (0, 0, sb_armor[0]); + } + } + + // face + Sbar_DrawFace (); + + // health + Sbar_DrawNum (136, 0, cl.stats[STAT_HEALTH], 3 + , cl.stats[STAT_HEALTH] <= 25); + + // ammo icon + if (rogue) + { + if (cl.items & RIT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.items & RIT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.items & RIT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.items & RIT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + else if (cl.items & RIT_LAVA_NAILS) + Sbar_DrawPic (224, 0, rsb_ammo[0]); + else if (cl.items & RIT_PLASMA_AMMO) + Sbar_DrawPic (224, 0, rsb_ammo[1]); + else if (cl.items & RIT_MULTI_ROCKETS) + Sbar_DrawPic (224, 0, rsb_ammo[2]); + } + else + { + if (cl.items & IT_SHELLS) + Sbar_DrawPic (224, 0, sb_ammo[0]); + else if (cl.items & IT_NAILS) + Sbar_DrawPic (224, 0, sb_ammo[1]); + else if (cl.items & IT_ROCKETS) + Sbar_DrawPic (224, 0, sb_ammo[2]); + else if (cl.items & IT_CELLS) + Sbar_DrawPic (224, 0, sb_ammo[3]); + } + + Sbar_DrawNum (248, 0, cl.stats[STAT_AMMO], 3, + cl.stats[STAT_AMMO] <= 10); + } + + //johnfitz -- removed the vid.width > 320 check here + if (cl.gametype == GAME_DEATHMATCH) + Sbar_MiniDeathmatchOverlay (); +} + +//============================================================================= + +/* +================== +Sbar_IntermissionNumber + +================== +*/ +void Sbar_IntermissionNumber (int x, int y, int num, int digits, int color) +{ + char str[12]; + char *ptr; + int l, frame; + + l = Sbar_itoa (num, str); + ptr = str; + if (l > digits) + ptr += (l-digits); + if (l < digits) + x += (digits-l)*24; + + while (*ptr) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + Draw_Pic (x,y,sb_nums[color][frame]); //johnfitz -- stretched menus + x += 24; + ptr++; + } +} + +/* +================== +Sbar_IntermissionPicForChar +================== +*/ +qpic_t *Sbar_IntermissionPicForChar (char c, int color) +{ + if ((unsigned)(c - '0') < 10) + return sb_nums[color][c - '0']; + if (c == '/') + return sb_slash; + if (c == ':') + return sb_colon; + if (c == '-') + return sb_nums[color][STAT_MINUS]; + return NULL; +} + +/* +================== +Sbar_IntermissionTextWidth +================== +*/ +int Sbar_IntermissionTextWidth (const char *str, int color) +{ + int len = 0; + while (*str) + { + qpic_t *pic = Sbar_IntermissionPicForChar (*str++, color); + len += pic ? pic->width : 24; + } + return len; +} + +/* +================== +Sbar_IntermissionText +================== +*/ +void Sbar_IntermissionText (int x, int y, const char *str, int color) +{ + while (*str) + { + qpic_t *pic = Sbar_IntermissionPicForChar (*str++, color); + if (!pic) + continue; + Draw_Pic (x, y, pic); + x += pic ? pic->width : 24; + } +} + +/* +================== +Sbar_DeathmatchOverlay +================== +*/ +void Sbar_DeathmatchOverlay (void) +{ + qpic_t *pic; + int i, k, l; + int top, bottom; + int x, y, f; + char num[12]; + scoreboard_t *s; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/ranking.lmp"); + M_DrawPic ((320-pic->width)/2, 8, pic); + +// scores + Sbar_SortFrags (); + +// draw the text + l = scoreboardlines; + + x = 80; //johnfitz -- simplified becuase some positioning is handled elsewhere + y = 40; + for (i = 0; i < l; i++) + { + k = fragsort[i]; + s = &cl.scores[k]; + if (!s->name[0]) + continue; + + // draw background + top = s->colors & 0xf0; + bottom = (s->colors & 15)<<4; + top = Sbar_ColorForMap (top); + bottom = Sbar_ColorForMap (bottom); + + Draw_Fill ( x, y, 40, 4, top, 1); //johnfitz -- stretched overlays + Draw_Fill ( x, y+4, 40, 4, bottom, 1); //johnfitz -- stretched overlays + + // draw number + f = s->frags; + sprintf (num, "%3i",f); + + Draw_Character ( x+8 , y, num[0]); //johnfitz -- stretched overlays + Draw_Character ( x+16 , y, num[1]); //johnfitz -- stretched overlays + Draw_Character ( x+24 , y, num[2]); //johnfitz -- stretched overlays + + if (k == cl.viewentity - 1) + Draw_Character ( x - 8, y, 12); //johnfitz -- stretched overlays + +#if 0 +{ + int total; + int n, minutes, tens, units; + + // draw time + total = cl.completed_time - s->entertime; + minutes = (int)total/60; + n = total - minutes*60; + tens = n/10; + units = n%10; + + sprintf (num, "%3i:%i%i", minutes, tens, units); + + M_Print ( x+48 , y, num); //johnfitz -- was Draw_String, changed for stretched overlays +} +#endif + + // draw name + M_Print (x+64, y, s->name); //johnfitz -- was Draw_String, changed for stretched overlays + + y += 10; + } + + GL_SetCanvas (CANVAS_SBAR); //johnfitz +} + +/* +================== +Sbar_MiniDeathmatchOverlay +================== +*/ +void Sbar_MiniDeathmatchOverlay (void) +{ + int i, k, top, bottom, x, y, f, numlines; + char num[12]; + float scale; //johnfitz + scoreboard_t *s; + + scale = CLAMP (1.0f, scr_sbarscale.value, (float)glwidth / 320.0f); //johnfitz + + //MAX_SCOREBOARDNAME = 32, so total width for this overlay plus sbar is 632, but we can cut off some i guess + if (glwidth/scale < 512 || scr_viewsize.value >= 120) //johnfitz -- test should consider scr_sbarscale + return; + +// scores + Sbar_SortFrags (); + +// draw the text + numlines = (scr_viewsize.value >= 110) ? 3 : 6; //johnfitz + + //find us + for (i = 0; i < scoreboardlines; i++) + if (fragsort[i] == cl.viewentity - 1) + break; + if (i == scoreboardlines) // we're not there + i = 0; + else // figure out start + i = i - numlines/2; + if (i > scoreboardlines - numlines) + i = scoreboardlines - numlines; + if (i < 0) + i = 0; + + x = 324; + y = (scr_viewsize.value >= 110) ? 24 : 0; //johnfitz -- start at the right place + for ( ; i < scoreboardlines && y <= 48; i++, y+=8) //johnfitz -- change y init, test, inc + { + k = fragsort[i]; + s = &cl.scores[k]; + if (!s->name[0]) + continue; + + // colors + top = s->colors & 0xf0; + bottom = (s->colors & 15)<<4; + top = Sbar_ColorForMap (top); + bottom = Sbar_ColorForMap (bottom); + + Draw_Fill (x, y+1, 40, 4, top, 1); + Draw_Fill (x, y+5, 40, 3, bottom, 1); + + // number + f = s->frags; + sprintf (num, "%3i",f); + Draw_Character (x+ 8, y, num[0]); + Draw_Character (x+16, y, num[1]); + Draw_Character (x+24, y, num[2]); + + // brackets + if (k == cl.viewentity - 1) + { + Draw_Character (x, y, 16); + Draw_Character (x+32, y, 17); + } + + // name + Draw_String (x+48, y, s->name); + } +} + +/* +================== +Sbar_IntermissionOverlay +================== +*/ +void Sbar_IntermissionOverlay (void) +{ + qpic_t *pic; + char time[32]; + char secrets[32]; + char monsters[32]; + int ltime, lsecrets, lmonsters; + int total; + + if (cl.gametype == GAME_DEATHMATCH) + { + Sbar_DeathmatchOverlay (); + return; + } + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + q_snprintf (time, sizeof (time), "%d:%02d", cl.completed_time / 60, cl.completed_time % 60); + q_snprintf (secrets, sizeof (secrets), "%d/%2d", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]); + q_snprintf (monsters, sizeof (monsters), "%d/%2d", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); + + ltime = Sbar_IntermissionTextWidth (time, 0); + lsecrets = Sbar_IntermissionTextWidth (secrets, 0); + lmonsters = Sbar_IntermissionTextWidth (monsters, 0); + + total = q_max (ltime, lsecrets); + total = q_max (lmonsters, total); + + pic = Draw_CachePic ("gfx/inter.lmp"); + total += pic->width + 24; + total = q_min (320, total); + Draw_Pic (160 - total / 2, 56, pic); + + pic = Draw_CachePic ("gfx/complete.lmp"); + Draw_Pic (160 - pic->width / 2, 24, pic); + + Sbar_IntermissionText (160 + total / 2 - ltime, 64, time, 0); + Sbar_IntermissionText (160 + total / 2 - lsecrets, 104, secrets, 0); + Sbar_IntermissionText (160 + total / 2 - lmonsters, 144, monsters, 0); +} + + +/* +================== +Sbar_FinaleOverlay +================== +*/ +void Sbar_FinaleOverlay (void) +{ + qpic_t *pic; + + GL_SetCanvas (CANVAS_MENU); //johnfitz + + pic = Draw_CachePic ("gfx/finale.lmp"); + Draw_Pic ( (320 - pic->width)/2, 16, pic); //johnfitz -- stretched menus +} + diff --git a/Quake/sbar.hpp b/Quake/sbar.hpp new file mode 100644 index 0000000..bce73b5 --- /dev/null +++ b/Quake/sbar.hpp @@ -0,0 +1,46 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_SBAR_H +#define _QUAKE_SBAR_H + +// the status bar is only redrawn if something has changed, but if anything +// does, the entire thing will be redrawn for the next vid.numpages frames. + +extern int sb_lines; // scan lines to draw + +void Sbar_Init (void); +void Sbar_LoadPics (void); + +void Sbar_Changed (void); +// call whenever any of the client stats represented on the sbar changes + +void Sbar_Draw (void); +// called every frame by screen + +void Sbar_IntermissionOverlay (void); +// called each frame after the level has been completed + +void Sbar_FinaleOverlay (void); + +#endif /* _QUAKE_SBAR_H */ + diff --git a/Quake/screen.hpp b/Quake/screen.hpp new file mode 100644 index 0000000..73c888c --- /dev/null +++ b/Quake/screen.hpp @@ -0,0 +1,84 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_SCREEN_H +#define _QUAKE_SCREEN_H + +// screen.h + +void SCR_Init (void); +void SCR_LoadPics (void); + +void SCR_UpdateScreen (void); + + +void SCR_SizeUp (void); +void SCR_SizeDown (void); +void SCR_BringDownConsole (void); +void SCR_CenterPrint (const char *str); + +void SCR_BeginLoadingPlaque (void); +void SCR_EndLoadingPlaque (void); + +int SCR_ModalMessage (const char *text, float timeout); //johnfitz -- added timeout + +extern float scr_con_current; +extern float scr_conlines; // lines of console to display + +extern int sb_lines; + +extern int clearnotify; // set to 0 whenever notify text is drawn +extern qboolean scr_disabled_for_loading; +extern qboolean scr_skipupdate; + +extern cvar_t scr_viewsize; + +extern cvar_t scr_sbaralpha; //johnfitz + +void SCR_UpdateWholeScreen (void); + +//johnfitz -- stuff for 2d drawing control +typedef enum { + CANVAS_NONE, + CANVAS_DEFAULT, + CANVAS_CONSOLE, + CANVAS_MENU, + CANVAS_SBAR, + CANVAS_WARPIMAGE, + CANVAS_CROSSHAIR, + CANVAS_BOTTOMLEFT, + CANVAS_BOTTOMRIGHT, + CANVAS_TOPRIGHT, + CANVAS_INVALID = -1 +} canvastype; +extern cvar_t scr_menuscale; +extern cvar_t scr_sbarscale; +extern cvar_t scr_conwidth; +extern cvar_t scr_conscale; +extern cvar_t scr_scale; +extern cvar_t scr_crosshairscale; +//johnfitz + +extern int scr_tileclear_updates; //johnfitz + +#endif /* _QUAKE_SCREEN_H */ + diff --git a/Quake/server.hpp b/Quake/server.hpp new file mode 100644 index 0000000..92e13b0 --- /dev/null +++ b/Quake/server.hpp @@ -0,0 +1,247 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef QUAKE_SERVER_H +#define QUAKE_SERVER_H + +// server.h + +typedef struct +{ + int maxclients; + int maxclientslimit; + struct client_s *clients; // [maxclients] + int serverflags; // episode completion information + qboolean changelevel_issued; // cleared when at SV_SpawnServer +} server_static_t; + +//============================================================================= + +#define MAX_SIGNON_BUFFERS 256 + +typedef enum {ss_loading, ss_active} server_state_t; + +typedef struct +{ + qboolean active; // false if only a net client + + qboolean paused; + qboolean loadgame; // handle connections specially + + double time; + + int lastcheck; // used by PF_checkclient + double lastchecktime; + + char name[64]; // map name + char modelname[64]; // maps/.bsp, for model_precache[0] + struct qmodel_s *worldmodel; + const char *model_precache[MAX_MODELS]; // NULL terminated + struct qmodel_s *models[MAX_MODELS]; + const char *sound_precache[MAX_SOUNDS]; // NULL terminated + const char *lightstyles[MAX_LIGHTSTYLES]; + int num_edicts; + int max_edicts; + edict_t *edicts; // can NOT be array indexed, because + // edict_t is variable sized, but can + // be used to reference the world ent + server_state_t state; // some actions are only valid during load + + sizebuf_t datagram; + byte datagram_buf[MAX_DATAGRAM]; + + sizebuf_t reliable_datagram; // copied to all clients at end of frame + byte reliable_datagram_buf[MAX_DATAGRAM]; + + sizebuf_t *signon; + int num_signon_buffers; + sizebuf_t *signon_buffers[MAX_SIGNON_BUFFERS]; + + unsigned protocol; //johnfitz + unsigned protocolflags; +} server_t; + + +#define NUM_PING_TIMES 16 +#define NUM_SPAWN_PARMS 16 + +enum sendsignon_e +{ + PRESPAWN_DONE, + PRESPAWN_FLUSH=1, + PRESPAWN_SIGNONBUFS, + PRESPAWN_SIGNONMSG, +}; + +typedef struct client_s +{ + qboolean active; // false = client is free + qboolean spawned; // false = don't send datagrams + qboolean dropasap; // has been told to go to another level + enum sendsignon_e sendsignon; // only valid before spawned + int signonidx; + + double last_message; // reliable messages must be sent + // periodically + + struct qsocket_s *netconnection; // communications handle + + usercmd_t cmd; // movement + vec3_t wishdir; // intended motion calced from cmd + + sizebuf_t message; // can be added to at any time, + // copied and clear once per frame + byte msgbuf[MAX_MSGLEN]; + edict_t *edict; // EDICT_NUM(clientnum+1) + char name[32]; // for printing to other people + int colors; + + float ping_times[NUM_PING_TIMES]; + int num_pings; // ping_times[num_pings%NUM_PING_TIMES] + +// spawn parms are carried from level to level + float spawn_parms[NUM_SPAWN_PARMS]; + +// client known data for deltas + int old_frags; +} client_t; + + +//============================================================================= + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +#define MOVETYPE_ANGLENOCLIP 1 +#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // gravity +#define MOVETYPE_STEP 4 // gravity, special edge handling +#define MOVETYPE_FLY 5 +#define MOVETYPE_TOSS 6 // gravity +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 +#define MOVETYPE_GIB 11 // 2021 rerelease gibs + +// edict->solid values +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block + +// edict->deadflag values +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// edict->flags +#define FL_FLY 1 +#define FL_SWIM 2 +//#define FL_GLIMPSE 4 +#define FL_CONVEYOR 4 +#define FL_CLIENT 8 +#define FL_INWATER 16 +#define FL_MONSTER 32 +#define FL_GODMODE 64 +#define FL_NOTARGET 128 +#define FL_ITEM 256 +#define FL_ONGROUND 512 +#define FL_PARTIALGROUND 1024 // not all corners are valid +#define FL_WATERJUMP 2048 // player jumping out of water +#define FL_JUMPRELEASED 4096 // for jump debouncing +#define FL_BUDDHAMODE 8192 + +// entity effects + +#define EF_BRIGHTFIELD 1 +#define EF_MUZZLEFLASH 2 +#define EF_BRIGHTLIGHT 4 +#define EF_DIMLIGHT 8 + +#define SPAWNFLAG_NOT_EASY 256 +#define SPAWNFLAG_NOT_MEDIUM 512 +#define SPAWNFLAG_NOT_HARD 1024 +#define SPAWNFLAG_NOT_DEATHMATCH 2048 + +//============================================================================ + +extern cvar_t teamplay; +extern cvar_t skill; +extern cvar_t deathmatch; +extern cvar_t coop; +extern cvar_t fraglimit; +extern cvar_t timelimit; + +extern server_static_t svs; // persistant server info +extern server_t sv; // local server + +extern client_t *host_client; + +extern edict_t *sv_player; + +//=========================================================== + +void SV_Init (void); + +void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count); +void SV_StartSound (edict_t *entity, int channel, const char *sample, int volume, + float attenuation); +void SV_LocalSound (client_t *client, const char *sample); // for 2021 rerelease + +void SV_DropClient (qboolean crash); + +void SV_SendClientMessages (void); +void SV_ClearDatagram (void); +void SV_ReserveSignonSpace (int numbytes); + +int SV_ModelIndex (const char *name); + +void SV_SetIdealPitch (void); + +void SV_AddUpdates (void); + +void SV_ClientThink (void); +void SV_AddClientToServer (struct qsocket_s *ret); + +void SV_ClientPrintf (const char *fmt, ...) FUNC_PRINTF(1,2); +void SV_BroadcastPrintf (const char *fmt, ...) FUNC_PRINTF(1,2); + +void SV_Physics (void); + +qboolean SV_CheckBottom (edict_t *ent); +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink); + +void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg); + +void SV_MoveToGoal (void); + +void SV_CheckForNewClients (void); +void SV_RunClients (void); +void SV_SaveSpawnparms (void); +void SV_SpawnServer (const char *server); + +#endif /* QUAKE_SERVER_H */ diff --git a/Quake/snd_codec.cpp b/Quake/snd_codec.cpp new file mode 100644 index 0000000..a64eec7 --- /dev/null +++ b/Quake/snd_codec.cpp @@ -0,0 +1,331 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.hpp" +#include "snd_codec.hpp" +#include "snd_codeci.hpp" + +/* headers for individual codecs */ +#include "snd_mikmod.hpp" +#include "snd_modplug.hpp" +#include "snd_xmp.hpp" +#include "snd_umx.hpp" +#include "snd_wave.hpp" +#include "snd_flac.hpp" +#include "snd_mp3.hpp" +#include "snd_vorbis.hpp" +#include "snd_opus.hpp" + + +static snd_codec_t *codecs; + +/* +================= +S_CodecRegister +================= +*/ +static void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit (void) +{ + snd_codec_t *codec; + codecs = NULL; + + /* Register in the inverse order + * of codec choice preference: */ +#ifdef USE_CODEC_UMX + S_CodecRegister(&umx_codec); +#endif +#ifdef USE_CODEC_MODPLUG + S_CodecRegister(&modplug_codec); +#endif +#ifdef USE_CODEC_MIKMOD + S_CodecRegister(&mikmod_codec); +#endif +#ifdef USE_CODEC_XMP + S_CodecRegister(&xmp_codec); +#endif +#ifdef USE_CODEC_WAVE + S_CodecRegister(&wav_codec); +#endif +#ifdef USE_CODEC_FLAC + S_CodecRegister(&flac_codec); +#endif +#ifdef USE_CODEC_MP3 + S_CodecRegister(&mp3_codec); +#endif +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&vorbis_codec); +#endif +#ifdef USE_CODEC_OPUS + S_CodecRegister(&opus_codec); +#endif + + codec = codecs; + while (codec) + { + codec->initialize(); + codec = codec->next; + } +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown (void) +{ + snd_codec_t *codec = codecs; + while (codec) + { + codec->shutdown(); + codec = codec->next; + } + codecs = NULL; +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type, qboolean loop) +{ + snd_codec_t *codec; + snd_stream_t *stream; + + if (type == CODECTYPE_NONE) + { + Con_Printf("Bad type for %s\n", filename); + return NULL; + } + + codec = codecs; + while (codec) + { + if (type == codec->type) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown type for %s\n", filename); + return NULL; + } + stream = S_CodecUtilOpen(filename, codec, loop); + if (stream) { + if (codec->codec_open(stream)) + stream->status = STREAM_PLAY; + else S_CodecUtilClose(&stream); + } + return stream; +} + +snd_stream_t *S_CodecOpenStreamExt (const char *filename, qboolean loop) +{ + snd_codec_t *codec; + snd_stream_t *stream; + const char *ext; + + ext = COM_FileGetExtension(filename); + if (! *ext) + { + Con_Printf("No extension for %s\n", filename); + return NULL; + } + + codec = codecs; + while (codec) + { + if (!q_strcasecmp(ext, codec->ext)) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown extension for %s\n", filename); + return NULL; + } + stream = S_CodecUtilOpen(filename, codec, loop); + if (stream) { + if (codec->codec_open(stream)) + stream->status = STREAM_PLAY; + else S_CodecUtilClose(&stream); + } + return stream; +} + +snd_stream_t *S_CodecOpenStreamAny (const char *filename, qboolean loop) +{ + snd_codec_t *codec; + snd_stream_t *stream; + const char *ext; + + ext = COM_FileGetExtension(filename); + if (! *ext) /* try all available */ + { + char tmp[MAX_QPATH]; + + codec = codecs; + while (codec) + { + q_snprintf(tmp, sizeof(tmp), "%s.%s", filename, codec->ext); + stream = S_CodecUtilOpen(tmp, codec, loop); + if (stream) { + if (codec->codec_open(stream)) { + stream->status = STREAM_PLAY; + return stream; + } + S_CodecUtilClose(&stream); + } + codec = codec->next; + } + + return NULL; + } + else /* use the name as is */ + { + codec = codecs; + while (codec) + { + if (!q_strcasecmp(ext, codec->ext)) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown extension for %s\n", filename); + return NULL; + } + stream = S_CodecUtilOpen(filename, codec, loop); + if (stream) { + if (codec->codec_open(stream)) + stream->status = STREAM_PLAY; + else S_CodecUtilClose(&stream); + } + return stream; + } +} + +qboolean S_CodecForwardStream (snd_stream_t *stream, unsigned int type) +{ + snd_codec_t *codec = codecs; + + while (codec) + { + if (type == codec->type) + break; + codec = codec->next; + } + if (!codec) return false; + stream->codec = codec; + return codec->codec_open(stream); +} + +void S_CodecCloseStream (snd_stream_t *stream) +{ + stream->status = STREAM_NONE; + stream->codec->codec_close(stream); +} + +int S_CodecRewindStream (snd_stream_t *stream) +{ + return stream->codec->codec_rewind(stream); +} + +int S_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + if (stream->codec->codec_jump) { + return stream->codec->codec_jump(stream, to); + } + return -1; +} + +int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->codec_read(stream, bytes, buffer); +} + +/* Util functions (used by codecs) */ + +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean loop) +{ + snd_stream_t *stream; + FILE *handle; + qboolean pak; + long length; + + /* Try to open the file */ + length = (long) COM_FOpenFile(filename, &handle, NULL); + pak = file_from_pak; + if (length == -1) + { + Con_DPrintf("Couldn't open %s\n", filename); + return NULL; + } + + /* Allocate a stream, Z_Malloc zeroes its content */ + stream = (snd_stream_t *) Z_Malloc(sizeof(snd_stream_t)); + stream->codec = codec; + stream->loop = loop; + stream->fh.file = handle; + stream->fh.start = ftell(handle); + stream->fh.pos = 0; + stream->fh.length = length; + stream->fh.pak = stream->pak = pak; + q_strlcpy(stream->name, filename, MAX_QPATH); + + return stream; +} + +void S_CodecUtilClose(snd_stream_t **stream) +{ + fclose((*stream)->fh.file); + Z_Free(*stream); + *stream = NULL; +} + +int S_CodecIsAvailable (unsigned int type) +{ + snd_codec_t *codec = codecs; + while (codec) + { + if (type == codec->type) + return codec->initialized; + codec = codec->next; + } + return -1; +} + diff --git a/Quake/snd_codec.hpp b/Quake/snd_codec.hpp new file mode 100644 index 0000000..fcc9f86 --- /dev/null +++ b/Quake/snd_codec.hpp @@ -0,0 +1,107 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +typedef struct snd_info_s +{ + int rate; + int bits, width; + int channels; + int samples; + int blocksize; + int size; + int dataofs; +} snd_info_t; + +typedef enum { + STREAM_NONE = -1, + STREAM_INIT, + STREAM_PAUSE, + STREAM_PLAY +} stream_status_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + fshandle_t fh; + qboolean pak; + char name[MAX_QPATH]; /* name of the source file */ + snd_info_t info; + stream_status_t status; + snd_codec_t *codec; /* codec handling this stream */ + qboolean loop; + void *priv; /* data private to the codec. */ +} snd_stream_t; + + +void S_CodecInit (void); +void S_CodecShutdown (void); + +/* Callers of the following S_CodecOpenStream* functions + * are reponsible for attaching any path to the filename */ + +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type, + qboolean loop); + /* Decides according to the required type. */ + +snd_stream_t *S_CodecOpenStreamAny (const char *filename, qboolean loop); + /* Decides according to file extension. if the + * name has no extension, try all available. */ + +snd_stream_t *S_CodecOpenStreamExt (const char *filename, qboolean loop); + /* Decides according to file extension. the name + * MUST have an extension. */ + +void S_CodecCloseStream (snd_stream_t *stream); +int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer); +int S_CodecRewindStream (snd_stream_t *stream); +int S_CodecJumpToOrder (snd_stream_t *stream, int to); + +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec, qboolean loop); +void S_CodecUtilClose(snd_stream_t **stream); + + +#define CODECTYPE_NONE 0 +#define CODECTYPE_MID (1U << 0) +#define CODECTYPE_MOD (1U << 1) +#define CODECTYPE_FLAC (1U << 2) +#define CODECTYPE_WAV (1U << 3) +#define CODECTYPE_MP3 (1U << 4) +#define CODECTYPE_VORBIS (1U << 5) +#define CODECTYPE_OPUS (1U << 6) +#define CODECTYPE_UMX (1U << 7) + +#define CODECTYPE_WAVE CODECTYPE_WAV +#define CODECTYPE_MIDI CODECTYPE_MID + +int S_CodecIsAvailable (unsigned int type); + /* return 1 if available, 0 if codec failed init + * or -1 if no such codec is present. */ + +#endif /* _SND_CODEC_H_ */ + diff --git a/Quake/snd_codeci.hpp b/Quake/snd_codeci.hpp new file mode 100644 index 0000000..4df7031 --- /dev/null +++ b/Quake/snd_codeci.hpp @@ -0,0 +1,57 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _SND_CODECI_H_ +#define _SND_CODECI_H_ + +/* Codec internals */ +typedef qboolean (*CODEC_INIT)(void); +typedef void (*CODEC_SHUTDOWN)(void); +typedef qboolean (*CODEC_OPEN)(snd_stream_t *stream); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef int (*CODEC_REWIND)(snd_stream_t *stream); +typedef int (*CODEC_JUMP)(snd_stream_t *stream, int order); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +struct snd_codec_s +{ + unsigned int type; /* handled data type. (1U << n) */ + qboolean initialized; /* init succeedded */ + const char *ext; /* expected extension */ + CODEC_INIT initialize; + CODEC_SHUTDOWN shutdown; + CODEC_OPEN codec_open; + CODEC_READ codec_read; + CODEC_REWIND codec_rewind; + CODEC_JUMP codec_jump; + CODEC_CLOSE codec_close; + snd_codec_t *next; +}; + +qboolean S_CodecForwardStream (snd_stream_t *stream, unsigned int type); + /* Forward a stream to another codec of 'type' type. */ + +#endif /* _SND_CODECI_H_ */ + diff --git a/Quake/snd_dma.cpp b/Quake/snd_dma.cpp new file mode 100644 index 0000000..3c8b097 --- /dev/null +++ b/Quake/snd_dma.cpp @@ -0,0 +1,1065 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2011 O. Sezer +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// snd_dma.c -- main control for any streaming sound output device + +#include "quakedef.hpp" +#include "snd_codec.hpp" +#include "bgmusic.hpp" + +static void S_Play (void); +static void S_PlayVol (void); +static void S_SoundList (void); +static void S_Update_ (void); +void S_StopAllSounds (qboolean clear); +static void S_StopAllSoundsC (void); + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +channel_t snd_channels[MAX_CHANNELS]; +int total_channels; + +static int snd_blocked = 0; +static qboolean snd_initialized = false; + +static dma_t sn; +volatile dma_t *shm = NULL; + +vec3_t listener_origin; +vec3_t listener_forward; +vec3_t listener_right; +vec3_t listener_up; + +#define sound_nominal_clip_dist 1000.0 + +int soundtime; // sample PAIRS +int paintedtime; // sample PAIRS + +int s_rawend; +portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + + +#define MAX_SFX 1024 +static sfx_t *known_sfx = NULL; // hunk allocated [MAX_SFX] +static int num_sfx; + +static sfx_t *ambient_sfx[NUM_AMBIENTS]; + +static qboolean sound_started = false; + +cvar_t bgmvolume = {"bgmvolume", "1", CVAR_ARCHIVE}; +cvar_t sfxvolume = {"volume", "0.7", CVAR_ARCHIVE}; + +cvar_t precache = {"precache", "1", CVAR_NONE}; +cvar_t loadas8bit = {"loadas8bit", "0", CVAR_NONE}; + +cvar_t sndspeed = {"sndspeed", "11025", CVAR_NONE}; +cvar_t snd_mixspeed = {"snd_mixspeed", "44100", CVAR_NONE}; + +#if defined(_WIN32) +#define SND_FILTERQUALITY_DEFAULT "5" +#else +#define SND_FILTERQUALITY_DEFAULT "1" +#endif + +cvar_t snd_filterquality = {"snd_filterquality", SND_FILTERQUALITY_DEFAULT, + CVAR_NONE}; + +static cvar_t nosound = {"nosound", "0", CVAR_NONE}; +static cvar_t ambient_level = {"ambient_level", "0.3", CVAR_NONE}; +static cvar_t ambient_fade = {"ambient_fade", "100", CVAR_NONE}; +static cvar_t snd_noextraupdate = {"snd_noextraupdate", "0", CVAR_NONE}; +static cvar_t snd_show = {"snd_show", "0", CVAR_NONE}; +static cvar_t _snd_mixahead = {"_snd_mixahead", "0.1", CVAR_ARCHIVE}; + + +static void S_SoundInfo_f (void) +{ + if (!sound_started || !shm) + { + Con_Printf ("sound system not started\n"); + return; + } + + Con_Printf("%d bit, %s, %d Hz\n", shm->samplebits, + (shm->channels == 2) ? "stereo" : "mono", shm->speed); + Con_Printf("%5d samples\n", shm->samples); + Con_Printf("%5d samplepos\n", shm->samplepos); + Con_Printf("%5d submission_chunk\n", shm->submission_chunk); + Con_Printf("%5d total_channels\n", total_channels); + Con_Printf("%p dma buffer\n", shm->buffer); +} + + +static void SND_Callback_sfxvolume (cvar_t *var) +{ + SND_InitScaletable (); +} + +static void SND_Callback_snd_filterquality (cvar_t *var) +{ + if (snd_filterquality.value < 1 || snd_filterquality.value > 5) + { + Con_Printf ("snd_filterquality must be between 1 and 5\n"); + Cvar_SetQuick (&snd_filterquality, SND_FILTERQUALITY_DEFAULT); + } +} + +/* +================ +S_Startup +================ +*/ +void S_Startup (void) +{ + if (!snd_initialized) + return; + + sound_started = SNDDMA_Init(&sn); + + if (!sound_started) + { + Con_Printf("Failed initializing sound\n"); + } + else + { + Con_Printf("Audio: %d bit, %s, %d Hz\n", shm->samplebits, + (shm->channels == 2) ? "stereo" : "mono", shm->speed); + } +} + + +/* +================ +S_Init +================ +*/ +void S_Init (void) +{ + int i; + + if (snd_initialized) + { + Con_Printf("Sound is already initialized\n"); + return; + } + + Cvar_RegisterVariable(&nosound); + Cvar_RegisterVariable(&sfxvolume); + Cvar_RegisterVariable(&precache); + Cvar_RegisterVariable(&loadas8bit); + Cvar_RegisterVariable(&bgmvolume); + Cvar_RegisterVariable(&ambient_level); + Cvar_RegisterVariable(&ambient_fade); + Cvar_RegisterVariable(&snd_noextraupdate); + Cvar_RegisterVariable(&snd_show); + Cvar_RegisterVariable(&_snd_mixahead); + Cvar_RegisterVariable(&sndspeed); + Cvar_RegisterVariable(&snd_mixspeed); + Cvar_RegisterVariable(&snd_filterquality); + + if (safemode || COM_CheckParm("-nosound")) + return; + + Con_Printf("\nSound Initialization\n"); + + Cmd_AddCommand("play", S_Play); + Cmd_AddCommand("playvol", S_PlayVol); + Cmd_AddCommand("stopsound", S_StopAllSoundsC); + Cmd_AddCommand("soundlist", S_SoundList); + Cmd_AddCommand("soundinfo", S_SoundInfo_f); + + i = COM_CheckParm("-sndspeed"); + if (i && i < com_argc-1) + { + Cvar_SetQuick (&sndspeed, com_argv[i + 1]); + } + + i = COM_CheckParm("-mixspeed"); + if (i && i < com_argc-1) + { + Cvar_SetQuick (&snd_mixspeed, com_argv[i + 1]); + } + + if (host_parms->memsize < 0x800000) + { + Cvar_SetQuick (&loadas8bit, "1"); + Con_Printf ("loading all sounds as 8bit\n"); + } + + Cvar_SetCallback(&sfxvolume, SND_Callback_sfxvolume); + Cvar_SetCallback(&snd_filterquality, &SND_Callback_snd_filterquality); + + SND_InitScaletable (); + + known_sfx = (sfx_t *) Hunk_AllocName (MAX_SFX*sizeof(sfx_t), "sfx_t"); + num_sfx = 0; + + snd_initialized = true; + + S_Startup (); + if (sound_started == 0) + return; + +// provides a tick sound until washed clean +// if (shm->buffer) +// shm->buffer[4] = shm->buffer[5] = 0x7f; // force a pop for debugging + + ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav"); + ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav"); + + S_CodecInit (); + + S_StopAllSounds (true); +} + + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= +void S_Shutdown (void) +{ + if (!sound_started) + return; + + sound_started = 0; + snd_blocked = 0; + + S_CodecShutdown(); + + SNDDMA_Shutdown(); + shm = NULL; +} + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================== +S_FindName + +================== +*/ +static sfx_t *S_FindName (const char *name) +{ + int i; + sfx_t *sfx; + + if (!name) + Sys_Error ("S_FindName: NULL"); + + if (strlen(name) >= MAX_QPATH) + Sys_Error ("Sound name too long: %s", name); + +// see if already loaded + for (i = 0; i < num_sfx; i++) + { + if (!strcmp(known_sfx[i].name, name)) + { + return &known_sfx[i]; + } + } + + if (num_sfx == MAX_SFX) + Sys_Error ("S_FindName: out of sfx_t"); + + sfx = &known_sfx[i]; + q_strlcpy (sfx->name, name, sizeof(sfx->name)); + + num_sfx++; + + return sfx; +} + + +/* +================== +S_TouchSound + +================== +*/ +void S_TouchSound (const char *name) +{ + sfx_t *sfx; + + if (!sound_started) + return; + + sfx = S_FindName (name); + Cache_Check (&sfx->cache); +} + +/* +================== +S_PrecacheSound + +================== +*/ +sfx_t *S_PrecacheSound (const char *name) +{ + sfx_t *sfx; + + if (!sound_started || nosound.value) + return NULL; + + sfx = S_FindName (name); + +// cache it in + if (precache.value) + S_LoadSound (sfx); + + return sfx; +} + + +//============================================================================= + +/* +================= +SND_PickChannel + +picks a channel based on priorities, empty slots, number of channels +================= +*/ +channel_t *SND_PickChannel (int entnum, int entchannel) +{ + int ch_idx; + int first_to_die; + int life_left; + +// Check for replacement sound, or find the best one to replace + first_to_die = -1; + life_left = 0x7fffffff; + for (ch_idx = NUM_AMBIENTS; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS; ch_idx++) + { + if (entchannel != 0 // channel 0 never overrides + && snd_channels[ch_idx].entnum == entnum + && (snd_channels[ch_idx].entchannel == entchannel || entchannel == -1) ) + { // always override sound from same entity + first_to_die = ch_idx; + break; + } + + // don't let monster sounds override player sounds + if (snd_channels[ch_idx].entnum == cl.viewentity && entnum != cl.viewentity && snd_channels[ch_idx].sfx) + continue; + + if (snd_channels[ch_idx].end - paintedtime < life_left) + { + life_left = snd_channels[ch_idx].end - paintedtime; + first_to_die = ch_idx; + } + } + + if (first_to_die == -1) + return NULL; + + if (snd_channels[first_to_die].sfx) + snd_channels[first_to_die].sfx = NULL; + + return &snd_channels[first_to_die]; +} + +/* +================= +SND_Spatialize + +spatializes a channel +================= +*/ +void SND_Spatialize (channel_t *ch) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + +// anything coming from the view entity will always be full volume + if (ch->entnum == cl.viewentity) + { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + return; + } + +// calculate stereo seperation and distance attenuation + VectorSubtract(ch->origin, listener_origin, source_vec); + dist = VectorNormalize(source_vec) * ch->dist_mult; + dot = DotProduct(listener_right, source_vec); + + if (shm->channels == 1) + { + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 1.0 + dot; + lscale = 1.0 - dot; + } + +// add in distance effect + scale = (1.0 - dist) * rscale; + ch->rightvol = (int) (ch->master_vol * scale); + if (ch->rightvol < 0) + ch->rightvol = 0; + + scale = (1.0 - dist) * lscale; + ch->leftvol = (int) (ch->master_vol * scale); + if (ch->leftvol < 0) + ch->leftvol = 0; +} + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +void S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) +{ + channel_t *target_chan, *check; + sfxcache_t *sc; + int ch_idx; + int skip; + + if (!sound_started) + return; + + if (!sfx) + return; + + if (nosound.value) + return; + +// pick a channel to play on + target_chan = SND_PickChannel(entnum, entchannel); + if (!target_chan) + return; + +// spatialize + memset (target_chan, 0, sizeof(*target_chan)); + VectorCopy(origin, target_chan->origin); + target_chan->dist_mult = attenuation / sound_nominal_clip_dist; + target_chan->master_vol = (int) (fvol * 255); + target_chan->entnum = entnum; + target_chan->entchannel = entchannel; + SND_Spatialize(target_chan); + + if (!target_chan->leftvol && !target_chan->rightvol) + return; // not audible at all + +// new channel + sc = S_LoadSound (sfx); + if (!sc) + { + target_chan->sfx = NULL; + return; // couldn't load the sound's data + } + + target_chan->sfx = sfx; + target_chan->pos = 0.0; + target_chan->end = paintedtime + sc->length; + +// if an identical sound has also been started this frame, offset the pos +// a bit to keep it from just making the first one louder + check = &snd_channels[NUM_AMBIENTS]; + for (ch_idx = NUM_AMBIENTS; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS; ch_idx++, check++) + { + if (check == target_chan) + continue; + if (check->sfx == sfx && !check->pos) + { + /* + skip = rand () % (int)(0.1 * shm->speed); + if (skip >= target_chan->end) + skip = target_chan->end - 1; + */ + /* LordHavoc: fixed skip calculations */ + skip = 0.1 * shm->speed; /* 0.1 * sc->speed */ + if (skip > sc->length) + skip = sc->length; + if (skip > 0) + skip = rand() % skip; + target_chan->pos += skip; + target_chan->end -= skip; + break; + } + } +} + +void S_StopSound (int entnum, int entchannel) +{ + int i; + + for (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) + { + if (snd_channels[i].entnum == entnum + && snd_channels[i].entchannel == entchannel) + { + snd_channels[i].end = 0; + snd_channels[i].sfx = NULL; + return; + } + } +} + +void S_StopAllSounds (qboolean clear) +{ + int i; + + if (!sound_started) + return; + + total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics + + for (i = 0; i < MAX_CHANNELS; i++) + { + if (snd_channels[i].sfx) + snd_channels[i].sfx = NULL; + } + + memset(snd_channels, 0, MAX_CHANNELS * sizeof(channel_t)); + + if (clear) + S_ClearBuffer (); +} + +static void S_StopAllSoundsC (void) +{ + S_StopAllSounds (true); +} + +void S_ClearBuffer (void) +{ + int clear; + + if (!sound_started || !shm) + return; + + SNDDMA_LockBuffer (); + if (! shm->buffer) + return; + + s_rawend = 0; + + if (shm->samplebits == 8 && !shm->signed8) + clear = 0x80; + else + clear = 0; + + memset(shm->buffer, clear, shm->samples * shm->samplebits / 8); + + SNDDMA_Submit (); +} + + +/* +================= +S_StaticSound +================= +*/ +void S_StaticSound (sfx_t *sfx, vec3_t origin, float vol, float attenuation) +{ + channel_t *ss; + sfxcache_t *sc; + + if (!sfx) + return; + + if (total_channels == MAX_CHANNELS) + { + Con_Printf ("total_channels == MAX_CHANNELS\n"); + return; + } + + ss = &snd_channels[total_channels]; + total_channels++; + + sc = S_LoadSound (sfx); + if (!sc) + return; + + if (sc->loopstart == -1) + { + Con_Printf ("Sound %s not looped\n", sfx->name); + return; + } + + ss->sfx = sfx; + VectorCopy (origin, ss->origin); + ss->master_vol = (int)vol; + ss->dist_mult = (attenuation / 64) / sound_nominal_clip_dist; + ss->end = paintedtime + sc->length; + + SND_Spatialize (ss); +} + + +//============================================================================= + +/* +=================== +S_UpdateAmbientSounds +=================== +*/ +static void S_UpdateAmbientSounds (void) +{ + mleaf_t *l; + int vol, ambient_channel; + channel_t *chan; + +// no ambients when disconnected + if (cls.state != ca_connected) + return; +// calc ambient sound levels + if (!cl.worldmodel) + return; + + l = Mod_PointInLeaf (listener_origin, cl.worldmodel); + if (!l || !ambient_level.value) + { + for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++) + snd_channels[ambient_channel].sfx = NULL; + return; + } + + for (ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++) + { + chan = &snd_channels[ambient_channel]; + chan->sfx = ambient_sfx[ambient_channel]; + + vol = (int) (ambient_level.value * l->ambient_sound_level[ambient_channel]); + if (vol < 8) + vol = 0; + + // don't adjust volume too fast + if (chan->master_vol < vol) + { + chan->master_vol += (int) (host_frametime * ambient_fade.value); + if (chan->master_vol > vol) + chan->master_vol = vol; + } + else if (chan->master_vol > vol) + { + chan->master_vol -= (int) (host_frametime * ambient_fade.value); + if (chan->master_vol < vol) + chan->master_vol = vol; + } + + chan->leftvol = chan->rightvol = chan->master_vol; + } +} + + +/* +=================== +S_RawSamples (from QuakeII) + +Streaming music support. Byte swapping +of data must be handled by the codec. +Expects data in signed 16 bit, or unsigned +8 bit format. +=================== +*/ +void S_RawSamples (int samples, int rate, int width, int channels, byte *data, float volume) +{ + int i; + int src, dst; + float scale; + int intVolume; + + if (s_rawend < paintedtime) + s_rawend = paintedtime; + + scale = (float) rate / shm->speed; + intVolume = (int) (256 * volume); + + if (channels == 2 && width == 2) + { + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + s_rawsamples [dst].left = ((short *) data)[src * 2] * intVolume; + s_rawsamples [dst].right = ((short *) data)[src * 2 + 1] * intVolume; + } + } + else if (channels == 1 && width == 2) + { + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + s_rawsamples [dst].left = ((short *) data)[src] * intVolume; + s_rawsamples [dst].right = ((short *) data)[src] * intVolume; + } + } + else if (channels == 2 && width == 1) + { + intVolume *= 256; + + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + // s_rawsamples [dst].left = ((signed char *) data)[src * 2] * intVolume; + // s_rawsamples [dst].right = ((signed char *) data)[src * 2 + 1] * intVolume; + s_rawsamples [dst].left = (((byte *) data)[src * 2] - 128) * intVolume; + s_rawsamples [dst].right = (((byte *) data)[src * 2 + 1] - 128) * intVolume; + } + } + else if (channels == 1 && width == 1) + { + intVolume *= 256; + + for (i = 0; ; i++) + { + src = i * scale; + if (src >= samples) + break; + dst = s_rawend & (MAX_RAW_SAMPLES - 1); + s_rawend++; + // s_rawsamples [dst].left = ((signed char *) data)[src] * intVolume; + // s_rawsamples [dst].right = ((signed char *) data)[src] * intVolume; + s_rawsamples [dst].left = (((byte *) data)[src] - 128) * intVolume; + s_rawsamples [dst].right = (((byte *) data)[src] - 128) * intVolume; + } + } +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) +{ + int i, j; + int total; + channel_t *ch; + channel_t *combine; + + if (!sound_started || (snd_blocked > 0)) + return; + + VectorCopy(origin, listener_origin); + VectorCopy(forward, listener_forward); + VectorCopy(right, listener_right); + VectorCopy(up, listener_up); + +// update general area ambient sound sources + S_UpdateAmbientSounds (); + + combine = NULL; + +// update spatialization for static and dynamic sounds + ch = snd_channels + NUM_AMBIENTS; + for (i = NUM_AMBIENTS; i < total_channels; i++, ch++) + { + if (!ch->sfx) + continue; + SND_Spatialize(ch); // respatialize channel + if (!ch->leftvol && !ch->rightvol) + continue; + + // try to combine static sounds with a previous channel of the same + // sound effect so we don't mix five torches every frame + + if (i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS) + { + // see if it can just use the last one + if (combine && combine->sfx == ch->sfx) + { + combine->leftvol += ch->leftvol; + combine->rightvol += ch->rightvol; + ch->leftvol = ch->rightvol = 0; + continue; + } + // search for one + combine = snd_channels + MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; + for (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; j < i; j++, combine++) + { + if (combine->sfx == ch->sfx) + break; + } + + if (j == total_channels) + { + combine = NULL; + } + else + { + if (combine != ch) + { + combine->leftvol += ch->leftvol; + combine->rightvol += ch->rightvol; + ch->leftvol = ch->rightvol = 0; + } + continue; + } + } + } + +// +// debugging output +// + if (snd_show.value) + { + total = 0; + ch = snd_channels; + for (i = 0; i < total_channels; i++, ch++) + { + if (ch->sfx && (ch->leftvol || ch->rightvol) ) + { + // Con_Printf ("%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name); + total++; + } + } + + Con_Printf ("----(%i)----\n", total); + } + +// add raw data from streamed samples +// BGM_Update(); // moved to the main loop just before S_Update () + +// mix some sound + S_Update_(); +} + +static void GetSoundtime (void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = shm->samples / shm->channels; + +// it is possible to miscount buffers if it has wrapped twice between +// calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + paintedtime = fullsamples; + S_StopAllSounds (true); + } + } + oldsamplepos = samplepos; + + soundtime = buffers*fullsamples + samplepos/shm->channels; +} + +void S_ExtraUpdate (void) +{ + if (snd_noextraupdate.value) + return; // don't pollute timings + S_Update_(); +} + +static void S_Update_ (void) +{ + unsigned int endtime; + int samps; + + if (!sound_started || (snd_blocked > 0)) + return; + + SNDDMA_LockBuffer (); + if (! shm->buffer) + return; + +// Updates DMA time + GetSoundtime(); + +// check to make sure that we haven't overshot + if (paintedtime < soundtime) + { + // Con_Printf ("S_Update_ : overflow\n"); + paintedtime = soundtime; + } + +// mix ahead of current position + endtime = soundtime + (unsigned int)(_snd_mixahead.value * shm->speed); + samps = shm->samples >> (shm->channels - 1); + endtime = q_min(endtime, (unsigned int)(soundtime + samps)); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); +} + +void S_BlockSound (void) +{ +/* FIXME: do we really need the blocking at the + * driver level? + */ + if (sound_started && snd_blocked == 0) /* ++snd_blocked == 1 */ + { + snd_blocked = 1; + S_ClearBuffer (); + if (shm) + SNDDMA_BlockSound(); + } +} + +void S_UnblockSound (void) +{ + if (!sound_started || !snd_blocked) + return; + if (snd_blocked == 1) /* --snd_blocked == 0 */ + { + snd_blocked = 0; + SNDDMA_UnblockSound(); + S_ClearBuffer (); + } +} + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +static void S_Play (void) +{ + static int hash = 345; + int i; + char name[256]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc()) + { + q_strlcpy(name, Cmd_Argv(i), sizeof(name)); + if (!strrchr(Cmd_Argv(i), '.')) + { + q_strlcat(name, ".wav", sizeof(name)); + } + sfx = S_PrecacheSound(name); + S_StartSound(hash++, 0, sfx, listener_origin, 1.0, 1.0); + i++; + } +} + +static void S_PlayVol (void) +{ + static int hash = 543; + int i; + float vol; + char name[256]; + sfx_t *sfx; + + i = 1; + while (i < Cmd_Argc()) + { + q_strlcpy(name, Cmd_Argv(i), sizeof(name)); + if (!strrchr(Cmd_Argv(i), '.')) + { + q_strlcat(name, ".wav", sizeof(name)); + } + sfx = S_PrecacheSound(name); + vol = atof(Cmd_Argv(i + 1)); + S_StartSound(hash++, 0, sfx, listener_origin, vol, 1.0); + i += 2; + } +} + +static void S_SoundList (void) +{ + int i; + sfx_t *sfx; + sfxcache_t *sc; + int size, total; + + total = 0; + for (sfx = known_sfx, i = 0; i < num_sfx; i++, sfx++) + { + sc = (sfxcache_t *) Cache_Check (&sfx->cache); + if (!sc) + continue; + size = sc->length*sc->width*(sc->stereo + 1); + total += size; + if (sc->loopstart >= 0) + Con_SafePrintf ("L"); //johnfitz -- was Con_Printf + else + Con_SafePrintf (" "); //johnfitz -- was Con_Printf + Con_SafePrintf("(%2db) %6i : %s\n", sc->width*8, size, sfx->name); //johnfitz -- was Con_Printf + } + Con_Printf ("%i sounds, %i bytes\n", num_sfx, total); //johnfitz -- added count +} + + +void S_LocalSound (const char *name) +{ + sfx_t *sfx; + + if (nosound.value) + return; + if (!sound_started) + return; + + sfx = S_PrecacheSound (name); + if (!sfx) + { + Con_Printf ("S_LocalSound: can't cache %s\n", name); + return; + } + S_StartSound (cl.viewentity, -1, sfx, vec3_origin, 1, 1); +} + + +void S_ClearPrecache (void) +{ +} + +void S_BeginPrecaching (void) +{ +} + +void S_EndPrecaching (void) +{ +} diff --git a/Quake/snd_flac.cpp b/Quake/snd_flac.cpp new file mode 100644 index 0000000..626b54a --- /dev/null +++ b/Quake/snd_flac.cpp @@ -0,0 +1,390 @@ +/* + * fLaC streaming music support, loosely based QuakeForge implementation + * with modifications. requires libFLAC >= 1.0.4 at compile and runtime. + * + * Copyright (C) 2005 Bill Currie + * Copyright (C) 2013 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_FLAC) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_flac.h" + +#undef LEGACY_FLAC +#include +/* FLAC 1.1.3 has FLAC_API_VERSION_CURRENT == 8 */ +#if !defined(FLAC_API_VERSION_CURRENT) || ((FLAC_API_VERSION_CURRENT+0) < 8) +#define LEGACY_FLAC +#include +#endif + +#ifdef LEGACY_FLAC +#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder +#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus +#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus +#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus +#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus + +#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new +#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish +#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete +#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single +#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute +#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata +#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state + +#define FLAC__STREAM_DECODER_INIT_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_OK +#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR +#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK /* !!! */ +#define FLAC__STREAM_DECODER_WRITE_STATUS_ABORT FLAC__STREAM_DECODER_WRITE_STATUS_ABORT +#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK +#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR +#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK +#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR +#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK +typedef unsigned FLAC_SIZE_T; +#else +typedef size_t FLAC_SIZE_T; +#endif + +typedef struct { + FLAC__StreamDecoder *decoder; + fshandle_t *file; + snd_info_t *info; + byte *buffer; + int size, pos, error; +} flacfile_t; + +/* CALLBACK FUNCTIONS: */ +static void +flac_error_func (const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + ff->error = -1; + Con_Printf ("FLAC: decoder error %i\n", status); +} + +static FLAC__StreamDecoderReadStatus +flac_read_func (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], + FLAC_SIZE_T *bytes, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (*bytes > 0) + { + *bytes = FS_fread(buffer, 1, *bytes, ff->file); + if (FS_ferror(ff->file)) + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + if (*bytes == 0) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; +} + +static FLAC__StreamDecoderSeekStatus +flac_seek_func (const FLAC__StreamDecoder *decoder, + FLAC__uint64 absolute_byte_offset, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (FS_fseek(ff->file, (long)absolute_byte_offset, SEEK_SET) < 0) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +static FLAC__StreamDecoderTellStatus +flac_tell_func (const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + long pos = FS_ftell (ff->file); + if (pos < 0) return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + *absolute_byte_offset = (FLAC__uint64) pos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +static FLAC__StreamDecoderLengthStatus +flac_length_func (const FLAC__StreamDecoder *decoder, + FLAC__uint64 *stream_length, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + *stream_length = (FLAC__uint64) FS_filelength (ff->file); + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool +flac_eof_func (const FLAC__StreamDecoder *decoder, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (FS_feof (ff->file)) return true; + return false; +} + +static FLAC__StreamDecoderWriteStatus +flac_write_func (const FLAC__StreamDecoder *decoder, + const FLAC__Frame *frame, const FLAC__int32 * const buffer[], + void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + + if (!ff->buffer) { + ff->buffer = (byte *) malloc (ff->info->blocksize * ff->info->channels * ff->info->width); + if (!ff->buffer) { + ff->error = -1; /* needn't set this here, but... */ + Con_Printf("Insufficient memory for fLaC audio\n"); + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + } + + if (ff->info->channels == 1) + { + unsigned i; + const FLAC__int32 *in = buffer[0]; + + if (ff->info->bits == 8) + { + byte *out = ff->buffer; + for (i = 0; i < frame->header.blocksize; i++) + *out++ = *in++ + 128; + } + else + { + short *out = (short *) ff->buffer; + for (i = 0; i < frame->header.blocksize; i++) + *out++ = *in++; + } + } + else + { + unsigned i; + const FLAC__int32 *li = buffer[0]; + const FLAC__int32 *ri = buffer[1]; + + if (ff->info->bits == 8) + { + char *lo = (char *) ff->buffer + 0; + char *ro = (char *) ff->buffer + 1; + for (i = 0; i < frame->header.blocksize; i++, lo++, ro++) + { + *lo++ = *li++ + 128; + *ro++ = *ri++ + 128; + } + } + else + { + short *lo = (short *) ff->buffer + 0; + short *ro = (short *) ff->buffer + 1; + for (i = 0; i < frame->header.blocksize; i++, lo++, ro++) + { + *lo++ = *li++; + *ro++ = *ri++; + } + } + } + + ff->size = frame->header.blocksize * ff->info->width * ff->info->channels; + ff->pos = 0; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static void +flac_meta_func (const FLAC__StreamDecoder *decoder, + const FLAC__StreamMetadata *metadata, void *client_data) +{ + flacfile_t *ff = (flacfile_t *) client_data; + if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) + { + ff->info->rate = metadata->data.stream_info.sample_rate; + ff->info->bits = metadata->data.stream_info.bits_per_sample; + ff->info->width = ff->info->bits / 8; + ff->info->channels = metadata->data.stream_info.channels; + ff->info->blocksize = metadata->data.stream_info.max_blocksize; + ff->info->dataofs = 0; /* got the STREAMINFO metadata */ + } +} + + +static qboolean S_FLAC_CodecInitialize (void) +{ + return true; +} + +static void S_FLAC_CodecShutdown (void) +{ +} + +static qboolean S_FLAC_CodecOpenStream (snd_stream_t *stream) +{ + flacfile_t *ff; + int rc; + + ff = (flacfile_t *) Z_Malloc(sizeof(flacfile_t)); + + ff->decoder = FLAC__stream_decoder_new (); + if (ff->decoder == NULL) + { + Con_Printf("Unable to create fLaC decoder\n"); + goto _fail; + } + + stream->priv = ff; + ff->info = & stream->info; + ff->file = & stream->fh; + ff->info->dataofs = -1; /* check for STREAMINFO metadata existence */ + +#ifdef LEGACY_FLAC + FLAC__seekable_stream_decoder_set_error_callback (ff->decoder, flac_error_func); + FLAC__seekable_stream_decoder_set_read_callback (ff->decoder, flac_read_func); + FLAC__seekable_stream_decoder_set_seek_callback (ff->decoder, flac_seek_func); + FLAC__seekable_stream_decoder_set_tell_callback (ff->decoder, flac_tell_func); + FLAC__seekable_stream_decoder_set_length_callback (ff->decoder, flac_length_func); + FLAC__seekable_stream_decoder_set_eof_callback (ff->decoder, flac_eof_func); + FLAC__seekable_stream_decoder_set_write_callback (ff->decoder, flac_write_func); + FLAC__seekable_stream_decoder_set_metadata_callback (ff->decoder, flac_meta_func); + FLAC__seekable_stream_decoder_set_client_data (ff->decoder, ff); + rc = FLAC__seekable_stream_decoder_init (ff->decoder); +#else + rc = FLAC__stream_decoder_init_stream(ff->decoder, + flac_read_func, + flac_seek_func, + flac_tell_func, + flac_length_func, + flac_eof_func, + flac_write_func, + flac_meta_func, + flac_error_func, + ff); +#endif + if (rc != FLAC__STREAM_DECODER_INIT_STATUS_OK) /* unlikely */ + { + Con_Printf ("FLAC: decoder init error %i\n", rc); + goto _fail; + } + + rc = FLAC__stream_decoder_process_until_end_of_metadata (ff->decoder); + if (rc == false || ff->error) + { + rc = FLAC__stream_decoder_get_state(ff->decoder); + Con_Printf("%s not a valid flac file? (decoder state %i)\n", + stream->name, rc); + goto _fail; + } + + if (ff->info->dataofs < 0) + { + Con_Printf("%s has no STREAMINFO\n", stream->name); + goto _fail; + } + if (ff->info->bits != 8 && ff->info->bits != 16) + { + Con_Printf("%s is not 8 or 16 bit\n", stream->name); + goto _fail; + } + if (ff->info->channels != 1 && ff->info->channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + ff->info->channels, stream->name); + goto _fail; + } + + return true; + +_fail: + if (ff->decoder) + { + FLAC__stream_decoder_finish (ff->decoder); + FLAC__stream_decoder_delete (ff->decoder); + } + Z_Free(ff); + return false; +} + +static int S_FLAC_CodecReadStream (snd_stream_t *stream, int len, void *buffer) +{ + flacfile_t *ff = (flacfile_t *) stream->priv; + byte *buf = (byte *) buffer; + int count = 0; + + while (len) { + int res = 0; + if (ff->size == ff->pos) + FLAC__stream_decoder_process_single (ff->decoder); + if (ff->error) return -1; + res = ff->size - ff->pos; + if (res > len) + res = len; + if (res > 0) { + memcpy (buf, ff->buffer + ff->pos, res); + count += res; + len -= res; + buf += res; + ff->pos += res; + } else if (res < 0) { /* error */ + return -1; + } else { + Con_DPrintf ("FLAC: EOF\n"); + break; + } + } + return count; +} + +static void S_FLAC_CodecCloseStream (snd_stream_t *stream) +{ + flacfile_t *ff = (flacfile_t *) stream->priv; + + FLAC__stream_decoder_finish (ff->decoder); + FLAC__stream_decoder_delete (ff->decoder); + + if (ff->buffer) free(ff->buffer); + Z_Free(ff); + + S_CodecUtilClose(&stream); +} + +static int S_FLAC_CodecRewindStream (snd_stream_t *stream) +{ + flacfile_t *ff = (flacfile_t *) stream->priv; + + ff->pos = ff->size = 0; + if (FLAC__stream_decoder_seek_absolute(ff->decoder, 0)) return 0; + return -1; +} + +snd_codec_t flac_codec = +{ + CODECTYPE_FLAC, + true, /* always available. */ + "flac", + S_FLAC_CodecInitialize, + S_FLAC_CodecShutdown, + S_FLAC_CodecOpenStream, + S_FLAC_CodecReadStream, + S_FLAC_CodecRewindStream, + NULL, /* jump */ + S_FLAC_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_FLAC */ + diff --git a/Quake/snd_flac.hpp b/Quake/snd_flac.hpp new file mode 100644 index 0000000..4cedf82 --- /dev/null +++ b/Quake/snd_flac.hpp @@ -0,0 +1,13 @@ +/* fLaC streaming music support. */ + +#if !defined(_SND_FLAC_H_) +#define _SND_FLAC_H_ 1 + +#if defined(USE_CODEC_FLAC) + +extern snd_codec_t flac_codec; + +#endif /* USE_CODEC_FLAC */ + +#endif /* ! _SND_FLAC_H_ */ + diff --git a/Quake/snd_mem.cpp b/Quake/snd_mem.cpp new file mode 100644 index 0000000..6f8783a --- /dev/null +++ b/Quake/snd_mem.cpp @@ -0,0 +1,360 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2011 O. Sezer + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// snd_mem.c: sound caching + +#include "quakedef.hpp" + +/* +================ +ResampleSfx +================ +*/ +static void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) +{ + int outcount; + int srcsample; + float stepscale; + int i; + int sample, fracstep; + sfxcache_t *sc; + + sc = (sfxcache_t *) Cache_Check (&sfx->cache); + if (!sc) + return; + + stepscale = (float)inrate / shm->speed; // this is usually 0.5, 1, or 2 + + outcount = sc->length / stepscale; + sc->length = outcount; + if (sc->loopstart != -1) + sc->loopstart = sc->loopstart / stepscale; + + sc->speed = shm->speed; + if (loadas8bit.value) + sc->width = 1; + else + sc->width = inwidth; + sc->stereo = 0; + +// resample / decimate to the current source rate + + if (stepscale == 1 && inwidth == 1 && sc->width == 1) + { +// fast special case + for (i = 0; i < outcount; i++) + ((signed char *)sc->data)[i] = (int)( (unsigned char)(data[i]) - 128); + } + else + { +// general case + // samplefrac can overflow 2**31 with very big sounds, see below. + int64_t samplefrac = 0; + fracstep = (int)(stepscale * 256); + for (i = 0; i < outcount; i++) + { + srcsample = (int)(samplefrac >> 8); + samplefrac += fracstep; // need int64_t here to prevent overflow... + if (inwidth == 2) + sample = LittleShort ( ((short *)data)[srcsample] ); + else + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + if (sc->width == 2) + ((short *)sc->data)[i] = sample; + else + ((signed char *)sc->data)[i] = sample >> 8; + } + } +} + +//============================================================================= + +/* +============== +S_LoadSound +============== +*/ +sfxcache_t *S_LoadSound (sfx_t *s) +{ + char namebuffer[256]; + byte *data; + wavinfo_t info; + int len; + float stepscale; + sfxcache_t *sc; + byte stackbuf[1*1024]; // avoid dirtying the cache heap + +// see if still in memory + sc = (sfxcache_t *) Cache_Check (&s->cache); + if (sc) + return sc; + +// Con_Printf ("S_LoadSound: %x\n", (int)stackbuf); + +// load it in + q_strlcpy(namebuffer, "sound/", sizeof(namebuffer)); + q_strlcat(namebuffer, s->name, sizeof(namebuffer)); + +// Con_Printf ("loading %s\n",namebuffer); + + data = COM_LoadStackFile(namebuffer, stackbuf, sizeof(stackbuf), NULL); + + if (!data) + { + Con_Printf ("Couldn't load %s\n", namebuffer); + return NULL; + } + + info = GetWavinfo (s->name, data, com_filesize); + if (info.channels != 1) + { + Con_Printf ("%s is a stereo sample\n",s->name); + return NULL; + } + + if (info.width != 1 && info.width != 2) + { + Con_Printf("%s is not 8 or 16 bit\n", s->name); + return NULL; + } + + stepscale = (float)info.rate / shm->speed; + len = info.samples / stepscale; + + len = len * info.width * info.channels; + + if (info.samples == 0 || len == 0) + { + Con_Printf("%s has zero samples\n", s->name); + return NULL; + } + + sc = (sfxcache_t *) Cache_Alloc ( &s->cache, len + sizeof(sfxcache_t), s->name); + if (!sc) + return NULL; + + sc->length = info.samples; + sc->loopstart = info.loopstart; + sc->speed = info.rate; + sc->width = info.width; + sc->stereo = info.channels; + + ResampleSfx (s, sc->speed, sc->width, data + info.dataofs); + + return sc; +} + + + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +static byte *data_p; +static byte *iff_end; +static byte *last_chunk; +static byte *iff_data; +static int iff_chunk_len; + +static short GetLittleShort (void) +{ + short val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + data_p += 2; + return val; +} + +static int GetLittleLong (void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +static void FindNextChunk (const char *name) +{ + while (1) + { + // Need at least 8 bytes for a chunk + if (last_chunk + 8 >= iff_end) + { + data_p = NULL; + return; + } + + data_p = last_chunk + 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0 || iff_chunk_len > iff_end - data_p) + { + data_p = NULL; + Con_DPrintf2("bad \"%s\" chunk length (%d)\n", name, iff_chunk_len); + return; + } + last_chunk = data_p + ((iff_chunk_len + 1) & ~1); + data_p -= 8; + if (!strncmp((char *)data_p, name, 4)) + return; + } +} + +static void FindChunk (const char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + +#if 0 +static void DumpChunks (void) +{ + char str[5]; + + str[4] = 0; + data_p = iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + Con_Printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} +#endif + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp((char *)data_p + 8, "WAVE", 4))) + { + Con_Printf("%s missing RIFF/WAVE chunks\n", name); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +#if 0 + DumpChunks (); +#endif + + FindChunk("fmt "); + if (!data_p) + { + Con_Printf("%s is missing fmt chunk\n", name); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != WAV_FORMAT_PCM) + { + Con_Printf("%s is not Microsoft PCM format\n", name); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4 + 2; + i = GetLittleShort(); + if (i != 8 && i != 16) + return info; + info.width = i / 8; + +// get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); + // Con_Printf("loopstart=%d\n", sfx->loopstart); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp((char *)data_p + 28, "mark", 4)) + { // this is not a proper parse, but it works with cooledit... + data_p += 24; + i = GetLittleLong(); // samples in loop + info.samples = info.loopstart + i; + // Con_Printf("looped length: %i\n", i); + } + } + } + else + info.loopstart = -1; + +// find data chunk + FindChunk("data"); + if (!data_p) + { + Con_Printf("%s is missing data chunk\n", name); + return info; + } + + data_p += 4; + samples = GetLittleLong() / info.width; + + if (info.samples) + { + if (samples < info.samples) + Sys_Error ("%s has a bad loop length", name); + } + else + info.samples = samples; + + if (info.loopstart >= info.samples) + { + Con_Warning ("%s has loop start >= end\n", name); + info.loopstart = -1; + info.samples = samples; + } + + info.dataofs = data_p - wav; + + return info; +} diff --git a/Quake/snd_mikmod.cpp b/Quake/snd_mikmod.cpp new file mode 100644 index 0000000..26561eb --- /dev/null +++ b/Quake/snd_mikmod.cpp @@ -0,0 +1,221 @@ +/* + * tracker music (module file) decoding support using libmikmod + * Copyright (C) 2013 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_MIKMOD) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_mikmod.h" +#include + +#if ((LIBMIKMOD_VERSION+0) < 0x030105) +#error libmikmod version is way too old and unusable. +#endif +#if (LIBMIKMOD_VERSION < 0x030107) /* ancient libmikmod */ +#define S_MIKMOD_initlib(c) MikMod_Init() +#else +#define S_MIKMOD_initlib(c) MikMod_Init(c) +#endif + +#ifndef DMODE_NOISEREDUCTION +#define DMODE_NOISEREDUCTION 0x1000 /* Low pass filtering */ +#endif +#ifndef DMODE_SIMDMIXER +#define DMODE_SIMDMIXER 0x0800 /* enable SIMD mixing */ +#endif + +typedef struct _mik_priv { + /* MREADER core members in libmikmod2/3: */ + int (*Seek)(struct MREADER*, long, int); + long (*Tell)(struct MREADER*); + BOOL (*Read)(struct MREADER*, void*, size_t); + int (*Get)(struct MREADER*); + BOOL (*Eof)(struct MREADER*); + /* no iobase members in libmikmod <= 3.2.0-beta2 */ + long iobase, prev_iobase; + + fshandle_t *fh; + MODULE *module; +} mik_priv_t; + +static int MIK_Seek (MREADER *r, long ofs, int whence) +{ + return FS_fseek(((mik_priv_t *)r)->fh, ofs, whence); +} + +static long MIK_Tell (MREADER *r) +{ + return FS_ftell(((mik_priv_t *)r)->fh); +} + +static BOOL MIK_Read (MREADER *r, void *ptr, size_t siz) +{ + return !!FS_fread(ptr, siz, 1, ((mik_priv_t *)r)->fh); +} + +static int MIK_Get (MREADER *r) +{ + return FS_fgetc(((mik_priv_t *)r)->fh); +} + +static BOOL MIK_Eof (MREADER *r) +{ + return FS_feof(((mik_priv_t *)r)->fh); +} + +static qboolean S_MIKMOD_CodecInitialize (void) +{ + if (mikmod_codec.initialized) + return true; + + /* set mode flags to only we like: */ + md_mode = 0; + if ((shm->samplebits / 8) == 2) + md_mode |= DMODE_16BITS; + if (shm->channels == 2) + md_mode |= DMODE_STEREO; + md_mode |= DMODE_SOFT_MUSIC; /* this is a software-only mixer */ + + /* md_mixfreq is UWORD, so something like 96000 isn't OK */ + md_mixfreq = (shm->speed < 65536)? shm->speed : 48000; + + /* keeping md_device as 0 which is default (auto-detect: we + * only register drv_nos, and it will be the only one found.) + * md_pansep (stereo channels separation) default 128 is OK. + * no reverbation (md_reverb 0 (up to 15)) is OK. + * md_musicvolume and md_sndfxvolume defaults are 128: OK. */ + /* just tone down overall volume md_volume from 128 to 96? */ + md_volume = 96; + + MikMod_RegisterDriver(&drv_nos); /* only need the "nosound" driver, none else */ + MikMod_RegisterAllLoaders(); + if (S_MIKMOD_initlib(NULL)) + { + Con_DPrintf("Could not initialize MikMod: %s\n", MikMod_strerror(MikMod_errno)); + return false; + } + + /* this can't get set with drv_nos, but whatever, be safe: */ + md_mode &= ~DMODE_SIMDMIXER; /* SIMD mixer is buggy when combined with HQMIXER */ + + mikmod_codec.initialized = true; + return true; +} + +static void S_MIKMOD_CodecShutdown (void) +{ + if (mikmod_codec.initialized) + { + mikmod_codec.initialized = false; + MikMod_Exit(); + } +} + +static qboolean S_MIKMOD_CodecOpenStream (snd_stream_t *stream) +{ + mik_priv_t *priv; + + stream->priv = Z_Malloc(sizeof(mik_priv_t)); + priv = (mik_priv_t *) stream->priv; + priv->Seek = MIK_Seek; + priv->Tell = MIK_Tell; + priv->Read = MIK_Read; + priv->Get = MIK_Get; + priv->Eof = MIK_Eof; + priv->fh = &stream->fh; + + priv->module = Player_LoadGeneric((MREADER *)stream->priv, 64, 0); + if (!priv->module) + { + Con_DPrintf("Could not load module: %s\n", MikMod_strerror(MikMod_errno)); + Z_Free(stream->priv); + return false; + } + + /* default values of module options set by Player_Init(): + * fadeout (0): don't fade out volume during when last position of the + * module is being played, + * extspd (1): process Protracker extended speed effect, + * panflag (1): process panning effects, + * wrap (0): don't wrap to restart position when module is finished, + * loop (1): process all in-module loops -- possible backward loops + * would make the module to loop endlessly. + */ + priv->module->wrap = stream->loop; + Player_Start(priv->module); + + stream->info.rate = md_mixfreq; + stream->info.bits = (md_mode & DMODE_16BITS)? 16: 8; + stream->info.width = stream->info.bits / 8; + stream->info.channels = (md_mode & DMODE_STEREO)? 2 : 1; +/* Con_DPrintf("Playing %s (%d chn)\n", priv->module->songname, priv->module->numchn);*/ + + return true; +} + +static int S_MIKMOD_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + if (!Player_Active()) + return 0; + + /* handle possible loop setting change: */ + ((mik_priv_t *)stream->priv)->module->wrap = stream->loop; + + return (int) VC_WriteBytes((SBYTE *)buffer, bytes); +} + +static void S_MIKMOD_CodecCloseStream (snd_stream_t *stream) +{ + Player_Stop(); + Player_Free(((mik_priv_t *)stream->priv)->module); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MIKMOD_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + Player_SetPosition ((UWORD)to); + return 0; +} + +static int S_MIKMOD_CodecRewindStream (snd_stream_t *stream) +{ + Player_SetPosition (0); /* FIXME: WRONG: THIS IS NOT A TIME SEEK */ + return 0; +} + +snd_codec_t mikmod_codec = +{ + CODECTYPE_MOD, + false, + "s3m", + S_MIKMOD_CodecInitialize, + S_MIKMOD_CodecShutdown, + S_MIKMOD_CodecOpenStream, + S_MIKMOD_CodecReadStream, + S_MIKMOD_CodecRewindStream, + S_MIKMOD_CodecJumpToOrder, + S_MIKMOD_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MIKMOD */ + diff --git a/Quake/snd_mikmod.hpp b/Quake/snd_mikmod.hpp new file mode 100644 index 0000000..9f0338b --- /dev/null +++ b/Quake/snd_mikmod.hpp @@ -0,0 +1,13 @@ +/* module tracker decoding support using libmikmod */ + +#if !defined(_SND_MIKMOD_H_) +#define _SND_MIKMOD_H_ + +#if defined(USE_CODEC_MIKMOD) + +extern snd_codec_t mikmod_codec; + +#endif /* USE_CODEC_MIKMOD */ + +#endif /* ! _SND_MIKMOD_H_ */ + diff --git a/Quake/snd_mix.cpp b/Quake/snd_mix.cpp new file mode 100644 index 0000000..9d29e9b --- /dev/null +++ b/Quake/snd_mix.cpp @@ -0,0 +1,527 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2011 O. Sezer +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "quakedef.hpp" + +#define PAINTBUFFER_SIZE 2048 +portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +int snd_scaletable[32][256]; +int *snd_p, snd_linear_count; +short *snd_out; + +static int snd_vol; + +static void Snd_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i = 0; i < snd_linear_count; i += 2) + { + val = snd_p[i] / 256; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i] = (short)0x8000; + else + snd_out[i] = val; + + val = snd_p[i+1] / 256; + 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; + } +} + +static void S_TransferStereo16 (int endtime) +{ + int lpos; + int lpaintedtime; + + snd_p = (int *) paintbuffer; + lpaintedtime = paintedtime; + + while (lpaintedtime < endtime) + { + // handle recirculating buffer issues + lpos = lpaintedtime & ((shm->samples >> 1) - 1); + + snd_out = (short *)shm->buffer + (lpos << 1); + + snd_linear_count = (shm->samples >> 1) - lpos; + if (lpaintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - lpaintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + Snd_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + lpaintedtime += (snd_linear_count >> 1); + } +} + +static void S_TransferPaintBuffer (int endtime) +{ + int out_idx, out_mask; + int count, step, val; + int *p; + + if (shm->samplebits == 16 && shm->channels == 2) + { + S_TransferStereo16 (endtime); + return; + } + + p = (int *) paintbuffer; + count = (endtime - paintedtime) * shm->channels; + out_mask = shm->samples - 1; + out_idx = paintedtime * shm->channels & out_mask; + step = 3 - shm->channels; + + if (shm->samplebits == 16) + { + short *out = (short *)shm->buffer; + while (count--) + { + val = *p / 256; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (shm->samplebits == 8 && !shm->signed8) + { + unsigned char *out = shm->buffer; + while (count--) + { + val = *p / 256; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val / 256) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (shm->samplebits == 8) /* S8 format, e.g. with Amiga AHI */ + { + signed char *out = (signed char *) shm->buffer; + while (count--) + { + val = *p / 256; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val / 256); + out_idx = (out_idx + 1) & out_mask; + } + } +} + +/* +============== +S_MakeBlackmanWindowKernel + +Makes a lowpass filter kernel, from equation 16-4 in +"The Scientist and Engineer's Guide to Digital Signal Processing" + +M is the kernel size (not counting the center point), must be even +kernel has room for M+1 floats +f_c is the filter cutoff frequency, as a fraction of the samplerate +============== +*/ +static void S_MakeBlackmanWindowKernel(float *kernel, int M, float f_c) +{ + int i; + for (i = 0; i <= M; i++) + { + if (i == M/2) + { + kernel[i] = 2 * M_PI * f_c; + } + else + { + kernel[i] = ( sin(2 * M_PI * f_c * (i - M/2.0)) / (i - (M/2.0)) ) + * (0.42 - 0.5*cos(2 * M_PI * i / (double)M) + + 0.08*cos(4 * M_PI * i / (double)M) ); + } + } + +// normalize the kernel so all of the values sum to 1 + { + float sum = 0; + for (i = 0; i <= M; i++) + { + sum += kernel[i]; + } + + for (i = 0; i <= M; i++) + { + kernel[i] /= sum; + } + } +} + +typedef struct { + float *memory; // kernelsize floats + float *kernel; // kernelsize floats + int kernelsize; // M+1, rounded up to be a multiple of 16 + int M; // M value used to make kernel, even + int parity; // 0-3 + float f_c; // cutoff frequency, [0..1], fraction of sample rate +} filter_t; + +static void S_UpdateFilter(filter_t *filter, int M, float f_c) +{ + if (filter->f_c != f_c || filter->M != M) + { + if (filter->memory != NULL) free(filter->memory); + if (filter->kernel != NULL) free(filter->kernel); + + filter->M = M; + filter->f_c = f_c; + + filter->parity = 0; + // M + 1 rounded up to the next multiple of 16 + filter->kernelsize = (M + 1) + 16 - ((M + 1) % 16); + filter->memory = (float *) calloc(filter->kernelsize, sizeof(float)); + filter->kernel = (float *) calloc(filter->kernelsize, sizeof(float)); + + S_MakeBlackmanWindowKernel(filter->kernel, M, f_c); + } +} + +/* +============== +S_ApplyFilter + +Lowpass-filter the given buffer containing 44100Hz audio. + +As an optimization, it decimates the audio to 11025Hz (setting every sample +position that's not a multiple of 4 to 0), then convoluting with the filter +kernel is 4x faster, because we can skip 3/4 of the input samples that are +known to be 0 and skip 3/4 of the filter kernel. +============== +*/ +static void S_ApplyFilter(filter_t *filter, int *data, int stride, int count) +{ + int i, j; + float *input; + const int kernelsize = filter->kernelsize; + const float *kernel = filter->kernel; + int parity; + + input = (float *) malloc(sizeof(float) * (filter->kernelsize + count)); + +// set up the input buffer +// memory holds the previous filter->kernelsize samples of input. + memcpy(input, filter->memory, filter->kernelsize * sizeof(float)); + + for (i=0; ikernelsize+i] = data[i * stride] / (32768.0 * 256.0); + } + +// copy out the last filter->kernelsize samples to 'memory' for next time + memcpy(filter->memory, input + count, filter->kernelsize * sizeof(float)); + +// apply the filter + parity = filter->parity; + + for (i=0; iparity = parity; + + free(input); +} + +/* +============== +S_LowpassFilter + +lowpass filters 24-bit integer samples in 'data' (stored in 32-bit ints). +assumes 44100Hz sample rate, and lowpasses at around 5kHz +memory should be a zero-filled filter_t struct +============== +*/ +static void S_LowpassFilter(int *data, int stride, int count, + filter_t *memory) +{ + int M; + float bw, f_c; + + switch ((int)snd_filterquality.value) + { + case 1: + M = 126; bw = 0.900; break; + case 2: + M = 150; bw = 0.915; break; + case 3: + M = 174; bw = 0.930; break; + case 4: + M = 198; bw = 0.945; break; + case 5: + default: + M = 222; bw = 0.960; break; + } + + f_c = (bw * 11025 / 2.0) / 44100.0; + + S_UpdateFilter(memory, M, f_c); + S_ApplyFilter(memory, data, stride, count); +} + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +static void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int endtime, int paintbufferstart); +static void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int endtime, int paintbufferstart); + +void S_PaintChannels (int endtime) +{ + int i; + int end, ltime, count; + channel_t *ch; + sfxcache_t *sc; + + snd_vol = sfxvolume.value * 256; + + while (paintedtime < endtime) + { + // if paintbuffer is smaller than DMA buffer + end = endtime; + if (endtime - paintedtime > PAINTBUFFER_SIZE) + end = paintedtime + PAINTBUFFER_SIZE; + + // clear the paint buffer + memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t)); + + // paint in the channels. + ch = snd_channels; + for (i = 0; i < total_channels; i++, ch++) + { + if (!ch->sfx) + continue; + if (!ch->leftvol && !ch->rightvol) + continue; + sc = S_LoadSound (ch->sfx); + if (!sc) + continue; + + ltime = paintedtime; + + while (ltime < end) + { // paint up to end + if (ch->end < end) + count = ch->end - ltime; + else + count = end - ltime; + + if (count > 0) + { + // the last param to SND_PaintChannelFrom is the index + // to start painting to in the paintbuffer, usually 0. + if (sc->width == 1) + SND_PaintChannelFrom8(ch, sc, count, ltime - paintedtime); + else + SND_PaintChannelFrom16(ch, sc, count, ltime - paintedtime); + + ltime += count; + } + + // if at end of loop, restart + if (ltime >= ch->end) + { + if (sc->loopstart >= 0) + { + ch->pos = sc->loopstart; + ch->end = ltime + sc->length - ch->pos; + } + else + { // channel just stopped + ch->sfx = NULL; + break; + } + } + } + } + + // clip each sample to 0dB, then reduce by 6dB (to leave some headroom for + // the lowpass filter and the music). the lowpass will smooth out the + // clipping + for (i=0; ispeed == 44100) + { + static filter_t memory_l, memory_r; + S_LowpassFilter((int *)paintbuffer, 2, end - paintedtime, &memory_l); + S_LowpassFilter(((int *)paintbuffer) + 1, 2, end - paintedtime, &memory_r); + } + + // paint in the music + if (s_rawend >= paintedtime) + { // copy from the streaming sound source + int s; + int stop; + + stop = (end < s_rawend) ? end : s_rawend; + + for (i = paintedtime; i < stop; i++) + { + s = i & (MAX_RAW_SAMPLES - 1); + // lower music by 6db to match sfx + paintbuffer[i - paintedtime].left += s_rawsamples[s].left / 2; + paintbuffer[i - paintedtime].right += s_rawsamples[s].right / 2; + } + // if (i != end) + // Con_Printf ("partial stream\n"); + // else + // Con_Printf ("full stream\n"); + } + + // transfer out according to DMA format + S_TransferPaintBuffer(end); + paintedtime = end; + } +} + +void SND_InitScaletable (void) +{ + int i, j; + int scale; + + for (i = 0; i < 32; i++) + { + scale = i * 8 * 256 * sfxvolume.value; + for (j = 0; j < 256; j++) + { + /* When compiling with gcc-4.1.0 at optimisations O1 and + higher, the tricky signed char type conversion is not + guaranteed. Therefore we explicity calculate the signed + value from the index as required. From Kevin Shanahan. + See: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=26719 + */ + // snd_scaletable[i][j] = ((signed char)j) * scale; + snd_scaletable[i][j] = ((j < 128) ? j : j - 256) * scale; + } + } +} + + +static void SND_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count, int paintbufferstart) +{ + int data; + int *lscale, *rscale; + unsigned char *sfx; + int i; + + if (ch->leftvol > 255) + ch->leftvol = 255; + if (ch->rightvol > 255) + ch->rightvol = 255; + + lscale = snd_scaletable[ch->leftvol >> 3]; + rscale = snd_scaletable[ch->rightvol >> 3]; + sfx = (unsigned char *)sc->data + ch->pos; + + for (i = 0; i < count; i++) + { + data = sfx[i]; + paintbuffer[paintbufferstart + i].left += lscale[data]; + paintbuffer[paintbufferstart + i].right += rscale[data]; + } + + ch->pos += count; +} + +static void SND_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int count, int paintbufferstart) +{ + int data; + int left, right; + int leftvol, rightvol; + signed short *sfx; + int i; + + leftvol = ch->leftvol * snd_vol; + rightvol = ch->rightvol * snd_vol; + leftvol /= 256; + rightvol /= 256; + sfx = (signed short *)sc->data + ch->pos; + + for (i = 0; i < count; i++) + { + data = sfx[i]; + // this was causing integer overflow as observed in quakespasm + // with the warpspasm mod moved >>8 to left/right volume above. + // left = (data * leftvol) >> 8; + // right = (data * rightvol) >> 8; + left = data * leftvol; + right = data * rightvol; + paintbuffer[paintbufferstart + i].left += left; + paintbuffer[paintbufferstart + i].right += right; + } + + ch->pos += count; +} + diff --git a/Quake/snd_modplug.cpp b/Quake/snd_modplug.cpp new file mode 100644 index 0000000..eb55785 --- /dev/null +++ b/Quake/snd_modplug.cpp @@ -0,0 +1,125 @@ +/* + * tracker music (module file) decoding support using libmodplug + * + * Copyright (C) 2013 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_MODPLUG) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_modplug.h" +#include + +static void S_MODPLUG_SetSettings (snd_stream_t *stream) +{ + ModPlug_Settings settings; + + ModPlug_GetSettings(&settings); + settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING; + settings.mChannels = shm->channels; + settings.mBits = shm->samplebits; + settings.mFrequency = shm->speed; + settings.mResamplingMode = MODPLUG_RESAMPLE_SPLINE;/*MODPLUG_RESAMPLE_FIR*/ + settings.mLoopCount = -1; /* to enable module internal loops */ + ModPlug_SetSettings(&settings); + + if (stream) { + stream->info.rate = shm->speed; + stream->info.bits = shm->samplebits; + stream->info.width = stream->info.bits / 8; + stream->info.channels = shm->channels; + } +} + +static qboolean S_MODPLUG_CodecInitialize (void) +{ + return true; +} + +static void S_MODPLUG_CodecShutdown (void) +{ +} + +static qboolean S_MODPLUG_CodecOpenStream (snd_stream_t *stream) +{ +/* need to load the whole file into memory and pass it to libmodplug */ + byte *moddata; + long len; + int mark; + + len = FS_filelength (&stream->fh); + mark = Hunk_LowMark(); + moddata = (byte *) Hunk_Alloc(len); + FS_fread(moddata, 1, len, &stream->fh); + + S_MODPLUG_SetSettings(stream); + stream->priv = ModPlug_Load(moddata, len); + Hunk_FreeToLowMark(mark); /* free original file data */ + if (!stream->priv) + { + Con_DPrintf("Could not load module %s\n", stream->name); + return false; + } + + ModPlug_Seek((ModPlugFile*)stream->priv, 0); + /* default volume (128) sounds rather low? */ + ModPlug_SetMasterVolume((ModPlugFile*)stream->priv, 384); /* 0-512 */ + return true; +} + +static int S_MODPLUG_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + return ModPlug_Read((ModPlugFile*)stream->priv, buffer, bytes); +} + +static void S_MODPLUG_CodecCloseStream (snd_stream_t *stream) +{ + ModPlug_Unload((ModPlugFile*)stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MODPLUG_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + ModPlug_SeekOrder((ModPlugFile*)stream->priv, to); + return 0; +} + +static int S_MODPLUG_CodecRewindStream (snd_stream_t *stream) +{ + ModPlug_Seek((ModPlugFile*)stream->priv, 0); + return 0; +} + +snd_codec_t modplug_codec = +{ + CODECTYPE_MOD, + true, /* always available. */ + "s3m", + S_MODPLUG_CodecInitialize, + S_MODPLUG_CodecShutdown, + S_MODPLUG_CodecOpenStream, + S_MODPLUG_CodecReadStream, + S_MODPLUG_CodecRewindStream, + S_MODPLUG_CodecJumpToOrder, + S_MODPLUG_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MODPLUG */ diff --git a/Quake/snd_modplug.hpp b/Quake/snd_modplug.hpp new file mode 100644 index 0000000..40f6397 --- /dev/null +++ b/Quake/snd_modplug.hpp @@ -0,0 +1,12 @@ +/* module tracker decoding support using libmodplug */ +#if !defined(_SND_MODPLUG_H_) +#define _SND_MODPLUG_H_ + +#if defined(USE_CODEC_MODPLUG) + +extern snd_codec_t modplug_codec; + +#endif /* USE_CODEC_MODPLUG */ + +#endif /* ! _SND_MODPLUG_H_ */ + diff --git a/Quake/snd_mp3.cpp b/Quake/snd_mp3.cpp new file mode 100644 index 0000000..21eb311 --- /dev/null +++ b/Quake/snd_mp3.cpp @@ -0,0 +1,461 @@ +/* + * MP3 decoding support using libmad: Adapted from the SoX library at + * http://sourceforge.net/projects/sox/, LGPLv2, Copyright (c) 2007-2009 + * SoX contributors, written by Fabrizio Gennari , + * with the decoding part based on the decoder tutorial program madlld + * written by Bertrand Petit (BSD license, see at + * http://www.bsd-dk.dk/~elrond/audio/madlld/). + * Adapted for use in Quake and Hexen II game engines by O.Sezer: + * Copyright (C) 2010-2019 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_MP3) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_mp3.h" +#include + +/* Under Windows, importing data from DLLs is a dicey proposition. This is true + * when using dlopen, but also true if linking directly against the DLL if the + * header does not mark the data as __declspec(dllexport), which mad.h does not. + * Sidestep the issue by defining our own mad_timer_zero. This is needed because + * mad_timer_zero is used in some of the mad.h macros. + */ +#define mad_timer_zero mad_timer_zero_stub +static mad_timer_t const mad_timer_zero_stub = {0, 0}; + +/* MAD returns values with MAD_F_FRACBITS (28) bits of precision, though it's + not certain that all of them are meaningful. Default to 16 bits to + align with most users expectation of output file should be 16 bits. */ +#define MP3_MAD_SAMPLEBITS 16 +#define MP3_MAD_SAMPLEWIDTH 2 +#define MP3_BUFFER_SIZE (5 * 8192) + +/* Private data */ +typedef struct _mp3_priv_t +{ + unsigned char mp3_buffer[MP3_BUFFER_SIZE]; + struct mad_stream Stream; + struct mad_frame Frame; + struct mad_synth Synth; + mad_timer_t Timer; + ptrdiff_t cursamp; + size_t FrameCount; +} mp3_priv_t; + + +/* (Re)fill the stream buffer that is to be decoded. If any data + * still exists in the buffer then they are first shifted to be + * front of the stream buffer. */ +static int mp3_inputdata(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t bytes_read; + size_t remaining; + + remaining = p->Stream.bufend - p->Stream.next_frame; + + /* libmad does not consume all the buffer it's given. Some + * data, part of a truncated frame, is left unused at the + * end of the buffer. That data must be put back at the + * beginning of the buffer and taken in account for + * refilling the buffer. This means that the input buffer + * must be large enough to hold a complete frame at the + * highest observable bit-rate (currently 448 kb/s). + * TODO: Is 2016 bytes the size of the largest frame? + * (448000*(1152/32000))/8 + */ + memmove(p->mp3_buffer, p->Stream.next_frame, remaining); + + bytes_read = FS_fread(p->mp3_buffer + remaining, 1, + MP3_BUFFER_SIZE - remaining, &stream->fh); + if (bytes_read == 0) + return -1; + + mad_stream_buffer(&p->Stream, p->mp3_buffer, bytes_read+remaining); + p->Stream.error = MAD_ERROR_NONE; + + return 0; +} + +static int mp3_startread(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t ReadSize; + + mad_stream_init(&p->Stream); + mad_frame_init(&p->Frame); + mad_synth_init(&p->Synth); + mad_timer_reset(&p->Timer); + + /* Decode at least one valid frame to find out the input + * format. The decoded frame will be saved off so that it + * can be processed later. + */ + ReadSize = FS_fread(p->mp3_buffer, 1, MP3_BUFFER_SIZE, &stream->fh); + if (!ReadSize || FS_ferror(&stream->fh)) + return -1; + + mad_stream_buffer(&p->Stream, p->mp3_buffer, ReadSize); + + /* Find a valid frame before starting up. This makes sure + * that we have a valid MP3. + */ + p->Stream.error = MAD_ERROR_NONE; + while (mad_frame_decode(&p->Frame,&p->Stream)) + { + /* check whether input buffer needs a refill */ + if (p->Stream.error == MAD_ERROR_BUFLEN) + { + if (mp3_inputdata(stream) == -1) + return -1;/* EOF with no valid data */ + + continue; + } + + /* We know that a valid frame hasn't been found yet + * so help libmad out and go back into frame seek mode. + */ + mad_stream_sync(&p->Stream); + p->Stream.error = MAD_ERROR_NONE; + } + + if (p->Stream.error) + { + Con_Printf("MP3: No valid MP3 frame found\n"); + return -1; + } + + switch(p->Frame.header.mode) + { + case MAD_MODE_SINGLE_CHANNEL: + case MAD_MODE_DUAL_CHANNEL: + case MAD_MODE_JOINT_STEREO: + case MAD_MODE_STEREO: + stream->info.channels = MAD_NCHANNELS(&p->Frame.header); + break; + default: + Con_Printf("MP3: Cannot determine number of channels\n"); + return -1; + } + + p->FrameCount = 1; + + mad_timer_add(&p->Timer,p->Frame.header.duration); + mad_synth_frame(&p->Synth,&p->Frame); + stream->info.rate = p->Synth.pcm.samplerate; + stream->info.bits = MP3_MAD_SAMPLEBITS; + stream->info.width = MP3_MAD_SAMPLEWIDTH; + + p->cursamp = 0; + + return 0; +} + +/* Read up to len samples from p->Synth + * If needed, read some more MP3 data, decode them and synth them + * Place in buf[]. + * Return number of samples read. */ +static int mp3_decode(snd_stream_t *stream, byte *buf, int len) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + int donow, i, done = 0; + mad_fixed_t sample; + int chan, x; + + do + { + x = (p->Synth.pcm.length - p->cursamp) * stream->info.channels; + donow = q_min(len, x); + i = 0; + while (i < donow) + { + for (chan = 0; chan < stream->info.channels; chan++) + { + sample = p->Synth.pcm.samples[chan][p->cursamp]; + /* convert from fixed to short, + * write in host-endian format. */ + if (sample <= -MAD_F_ONE) + sample = -0x7FFF; + else if (sample >= MAD_F_ONE) + sample = 0x7FFF; + else + sample >>= (MAD_F_FRACBITS + 1 - 16); + if (host_bigendian) + { + *buf++ = (sample >> 8) & 0xFF; + *buf++ = sample & 0xFF; + } + else /* assumed LITTLE_ENDIAN. */ + { + *buf++ = sample & 0xFF; + *buf++ = (sample >> 8) & 0xFF; + } + i++; + } + p->cursamp++; + } + + len -= donow; + done += donow; + + if (len == 0) + break; + + /* check whether input buffer needs a refill */ + if (p->Stream.error == MAD_ERROR_BUFLEN) + { + if (mp3_inputdata(stream) == -1) + { + /* check feof() ?? */ + Con_DPrintf("mp3 EOF\n"); + break; + } + } + + if (mad_frame_decode(&p->Frame, &p->Stream)) + { + if (MAD_RECOVERABLE(p->Stream.error)) + { + mad_stream_sync(&p->Stream); /* to frame seek mode */ + continue; + } + else + { + if (p->Stream.error == MAD_ERROR_BUFLEN) + continue; + else + { + Con_Printf("MP3: unrecoverable frame level error (%s)\n", + mad_stream_errorstr(&p->Stream)); + break; + } + } + } + p->FrameCount++; + mad_timer_add(&p->Timer, p->Frame.header.duration); + mad_synth_frame(&p->Synth, &p->Frame); + p->cursamp = 0; + } while (1); + + return done; +} + +static int mp3_stopread(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t*) stream->priv; + + mad_synth_finish(&p->Synth); + mad_frame_finish(&p->Frame); + mad_stream_finish(&p->Stream); + + return 0; +} + +static int mp3_madseek(snd_stream_t *stream, unsigned long offset) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t initial_bitrate = p->Frame.header.bitrate; + size_t consumed = 0; + int vbr = 0; /* Variable Bit Rate, bool */ + qboolean depadded = false; + unsigned long to_skip_samples = 0; + + /* Reset all */ + FS_rewind(&stream->fh); + mad_timer_reset(&p->Timer); + p->FrameCount = 0; + + /* They where opened in startread */ + mad_synth_finish(&p->Synth); + mad_frame_finish(&p->Frame); + mad_stream_finish(&p->Stream); + + mad_stream_init(&p->Stream); + mad_frame_init(&p->Frame); + mad_synth_init(&p->Synth); + + offset /= stream->info.channels; + to_skip_samples = offset; + + while (1) /* Read data from the MP3 file */ + { + int bytes_read, padding = 0; + size_t leftover = p->Stream.bufend - p->Stream.next_frame; + + memcpy(p->mp3_buffer, p->Stream.this_frame, leftover); + bytes_read = FS_fread(p->mp3_buffer + leftover, (size_t) 1, + MP3_BUFFER_SIZE - leftover, &stream->fh); + if (bytes_read <= 0) + { + Con_DPrintf("seek failure. unexpected EOF (frames=%lu leftover=%lu)\n", + (unsigned long)p->FrameCount, (unsigned long)leftover); + break; + } + for ( ; !depadded && padding < bytes_read && !p->mp3_buffer[padding]; ++padding) + ; + depadded = true; + mad_stream_buffer(&p->Stream, p->mp3_buffer + padding, leftover + bytes_read - padding); + + while (1) /* Decode frame headers */ + { + static unsigned short samples; + p->Stream.error = MAD_ERROR_NONE; + + /* Not an audio frame */ + if (mad_header_decode(&p->Frame.header, &p->Stream) == -1) + { + if (p->Stream.error == MAD_ERROR_BUFLEN) + break; /* Normal behaviour; get some more data from the file */ + if (!MAD_RECOVERABLE(p->Stream.error)) + { + Con_DPrintf("unrecoverable MAD error\n"); + break; + } + if (p->Stream.error == MAD_ERROR_LOSTSYNC) + { + Con_DPrintf("MAD lost sync\n"); + } + else + { + Con_DPrintf("recoverable MAD error\n"); + } + continue; + } + + consumed += p->Stream.next_frame - p->Stream.this_frame; + vbr |= (p->Frame.header.bitrate != initial_bitrate); + + samples = 32 * MAD_NSBSAMPLES(&p->Frame.header); + + p->FrameCount++; + mad_timer_add(&p->Timer, p->Frame.header.duration); + + if (to_skip_samples <= samples) + { + mad_frame_decode(&p->Frame,&p->Stream); + mad_synth_frame(&p->Synth, &p->Frame); + p->cursamp = to_skip_samples; + return 0; + } + else to_skip_samples -= samples; + + /* If not VBR, we can extrapolate frame size */ + if (p->FrameCount == 64 && !vbr) + { + p->FrameCount = offset / samples; + to_skip_samples = offset % samples; + if (0 != FS_fseek(&stream->fh, (p->FrameCount * consumed / 64), SEEK_SET)) + return -1; + + /* Reset Stream for refilling buffer */ + mad_stream_finish(&p->Stream); + mad_stream_init(&p->Stream); + break; + } + } + } + + return -1; +} + +static qboolean S_MP3_CodecInitialize (void) +{ + return true; +} + +static void S_MP3_CodecShutdown (void) +{ +} + +static qboolean S_MP3_CodecOpenStream (snd_stream_t *stream) +{ + int err; + + if (mp3_skiptags(stream) < 0) + { + Con_Printf("Corrupt mp3 file (bad tags.)\n"); + return false; + } + + stream->priv = calloc(1, sizeof(mp3_priv_t)); + if (!stream->priv) + { + Con_Printf("Insufficient memory for MP3 audio\n"); + return false; + } + err = mp3_startread(stream); + if (err != 0) + { + Con_Printf("%s is not a valid mp3 file\n", stream->name); + } + else if (stream->info.channels != 1 && stream->info.channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + stream->info.channels, stream->name); + } + else + { + return true; + } + free(stream->priv); + return false; +} + +static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int res = mp3_decode(stream, (byte *)buffer, bytes / stream->info.width); + return res * stream->info.width; +} + +static void S_MP3_CodecCloseStream (snd_stream_t *stream) +{ + mp3_stopread(stream); + free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MP3_CodecRewindStream (snd_stream_t *stream) +{ + /* + mp3_stopread(stream); + FS_rewind(&stream->fh); + return mp3_startread(stream); + */ + return mp3_madseek(stream, 0); +} + +snd_codec_t mp3_codec = +{ + CODECTYPE_MP3, + true, /* always available. */ + "mp3", + S_MP3_CodecInitialize, + S_MP3_CodecShutdown, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecRewindStream, + NULL, /* jump */ + S_MP3_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MP3 */ + diff --git a/Quake/snd_mp3.hpp b/Quake/snd_mp3.hpp new file mode 100644 index 0000000..b519a9d --- /dev/null +++ b/Quake/snd_mp3.hpp @@ -0,0 +1,14 @@ +/* MP3 decoding support using libmad or libmpg123. */ + +#if !defined(_SND_MP3_H_) +#define _SND_MP3_H_ + +#if defined(USE_CODEC_MP3) + +extern snd_codec_t mp3_codec; +int mp3_skiptags(snd_stream_t *); + +#endif /* USE_CODEC_MP3 */ + +#endif /* ! _SND_MP3_H_ */ + diff --git a/Quake/snd_mp3tag.cpp b/Quake/snd_mp3tag.cpp new file mode 100644 index 0000000..efca6a7 --- /dev/null +++ b/Quake/snd_mp3tag.cpp @@ -0,0 +1,405 @@ +/* MP3 TAGS STUFF -- put together using public specs. + * Copyright (C) 2018-2019 O. Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_MP3) +#include "snd_codec.h" +#include "q_ctype.h" + +static inline qboolean is_id3v1(const unsigned char *data, long length) { + /* http://id3.org/ID3v1 : 3 bytes "TAG" identifier and 125 bytes tag data */ + if (length < 128 || memcmp(data,"TAG",3) != 0) { + return false; + } + return true; +} +static qboolean is_id3v2(const unsigned char *data, size_t length) { + /* ID3v2 header is 10 bytes: http://id3.org/id3v2.4.0-structure */ + /* bytes 0-2: "ID3" identifier */ + if (length < 10 || memcmp(data,"ID3",3) != 0) { + return false; + } + /* bytes 3-4: version num (major,revision), each byte always less than 0xff. */ + if (data[3] == 0xff || data[4] == 0xff) { + return false; + } + /* bytes 6-9 are the ID3v2 tag size: a 32 bit 'synchsafe' integer, i.e. the + * highest bit 7 in each byte zeroed. i.e.: 7 bit information in each byte -> + * effectively a 28 bit value. */ + if (data[6] >= 0x80 || data[7] >= 0x80 || data[8] >= 0x80 || data[9] >= 0x80) { + return false; + } + return true; +} +static long get_id3v2_len(const unsigned char *data, long length) { + /* size is a 'synchsafe' integer (see above) */ + long size = (long)((data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]); + size += 10; /* header size */ + /* ID3v2 header[5] is flags (bits 4-7 only, 0-3 are zero). + * bit 4 set: footer is present (a copy of the header but + * with "3DI" as ident.) */ + if (data[5] & 0x10) { + size += 10; /* footer size */ + } + /* optional padding (always zeroes) */ + while (size < length && data[size] == 0) { + ++size; + } + return size; +} +static qboolean is_apetag(const unsigned char *data, size_t length) { + /* http://wiki.hydrogenaud.io/index.php?title=APEv2_specification + * Header/footer is 32 bytes: bytes 0-7 ident, bytes 8-11 version, + * bytes 12-17 size. bytes 24-31 are reserved: must be all zeroes. */ + unsigned int v; + + if (length < 32 || memcmp(data,"APETAGEX",8) != 0) { + return false; + } + v = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]); /* version */ + if (v != 2000U && v != 1000U) { + return false; + } + v = 0; /* reserved bits : */ + if (memcmp(&data[24],&v,4) != 0 || memcmp(&data[28],&v,4) != 0) { + return false; + } + return true; +} +static long get_ape_len(const unsigned char *data) { + unsigned int flags, version; + long size = (long)((data[15]<<24) | (data[14]<<16) | (data[13]<<8) | data[12]); + version = (unsigned)((data[11]<<24) | (data[10]<<16) | (data[9]<<8) | data[8]); + flags = (unsigned)((data[23]<<24) | (data[22]<<16) | (data[21]<<8) | data[20]); + if (version == 2000U && (flags & (1U<<31))) size += 32; /* header present. */ + return size; +} +static inline int is_lyrics3tag(const unsigned char *data, long length) { + /* http://id3.org/Lyrics3 + * http://id3.org/Lyrics3v2 */ + if (length < 15) return 0; + if (memcmp(data+6,"LYRICS200",9) == 0) return 2; /* v2 */ + if (memcmp(data+6,"LYRICSEND",9) == 0) return 1; /* v1 */ + return 0; +} +static long get_lyrics3v1_len(snd_stream_t *stream) { + const char *p; long i, len; + char buf[5104]; + /* needs manual search: http://id3.org/Lyrics3 */ + if (stream->fh.length < 20) return -1; + len = (stream->fh.length > 5109)? 5109 : stream->fh.length; + FS_fseek(&stream->fh, -len, SEEK_END); + FS_fread(buf, 1, (len -= 9), &stream->fh); /* exclude footer */ + /* strstr() won't work here. */ + for (i = len - 11, p = buf; i >= 0; --i, ++p) { + if (memcmp(p, "LYRICSBEGIN", 11) == 0) + break; + } + if (i < 0) return -1; + return len - (long)(p - buf) + 9 /* footer */; +} +static inline long get_lyrics3v2_len(const unsigned char *data, long length) { + /* 6 bytes before the end marker is size in decimal format - + * does not include the 9 bytes end marker and size field. */ + if (length != 6) return 0; + return strtol((const char *)data, NULL, 10) + 15; +} +static inline qboolean verify_lyrics3v2(const unsigned char *data, long length) { + if (length < 11) return false; + if (memcmp(data,"LYRICSBEGIN",11) == 0) return true; + return false; +} +#define MMTAG_PARANOID +static qboolean is_musicmatch(const unsigned char *data, long length) { + /* From docs/musicmatch.txt in id3lib: https://sourceforge.net/projects/id3lib/ + Overall tag structure: + + +-----------------------------+ + | Header | + | (256 bytes, OPTIONAL) | + +-----------------------------+ + | Image extension (4 bytes) | + +-----------------------------+ + | Image binary | + | (var. length >= 4 bytes) | + +-----------------------------+ + | Unused (4 bytes) | + +-----------------------------+ + | Version info (256 bytes) | + +-----------------------------+ + | Audio meta-data | + | (var. length >= 7868 bytes) | + +-----------------------------+ + | Data offsets (20 bytes) | + +-----------------------------+ + | Footer (48 bytes) | + +-----------------------------+ + */ + if (length < 48) return false; + /* sig: 19 bytes company name + 13 bytes space */ + if (memcmp(data,"Brava Software Inc. ",32) != 0) { + return false; + } + /* 4 bytes version: x.xx */ + if (!q_isdigit(data[32]) || data[33] != '.' || + !q_isdigit(data[34]) ||!q_isdigit(data[35])) { + return false; + } + #ifdef MMTAG_PARANOID + /* [36..47]: 12 bytes trailing space */ + for (length = 36; length < 48; ++length) { + if (data[length] != ' ') return false; + } + #endif + return true; +} +static long get_musicmatch_len(snd_stream_t *stream) { + const int metasizes[4] = { 7868, 7936, 8004, 8132 }; + const unsigned char syncstr[10] = {'1','8','2','7','3','6','4','5',0,0}; + unsigned char buf[256]; + int i, j, imgext_ofs, version_ofs; + long len; + + FS_fseek(&stream->fh, -68, SEEK_END); + FS_fread(buf, 1, 20, &stream->fh); + imgext_ofs = (int)((buf[3] <<24) | (buf[2] <<16) | (buf[1] <<8) | buf[0] ); + version_ofs = (int)((buf[15]<<24) | (buf[14]<<16) | (buf[13]<<8) | buf[12]); + if (version_ofs <= imgext_ofs) return -1; + if (version_ofs <= 0 || imgext_ofs <= 0) return -1; + /* Try finding the version info section: + * Because metadata section comes after it, and because metadata section + * has different sizes across versions (format ver. <= 3.00: always 7868 + * bytes), we can _not_ directly calculate using deltas from the offsets + * section. */ + for (i = 0; i < 4; ++i) { + /* 48: footer, 20: offsets, 256: version info */ + len = metasizes[i] + 48 + 20 + 256; + if (stream->fh.length < len) return -1; + FS_fseek(&stream->fh, -len, SEEK_END); + FS_fread(buf, 1, 256, &stream->fh); + /* [0..9]: sync string, [30..255]: 0x20 */ + #ifdef MMTAG_PARANOID + for (j = 30; j < 256; ++j) { + if (buf[j] != ' ') break; + } + if (j < 256) continue; + #endif + if (memcmp(buf, syncstr, 10) == 0) { + break; + } + } + if (i == 4) return -1; /* no luck. */ + #ifdef MMTAG_PARANOID + /* unused section: (4 bytes of 0x00) */ + FS_fseek(&stream->fh, -(len + 4), SEEK_END); + FS_fread(buf, 1, 4, &stream->fh); j = 0; + if (memcmp(buf, &j, 4) != 0) return -1; + #endif + len += (version_ofs - imgext_ofs); + if (stream->fh.length < len) return -1; + FS_fseek(&stream->fh, -len, SEEK_END); + FS_fread(buf, 1, 8, &stream->fh); + j = (int)((buf[7] <<24) | (buf[6] <<16) | (buf[5] <<8) | buf[4]); + if (j < 0) return -1; + /* verify image size: */ + /* without this, we may land at a wrong place. */ + if (j + 12 != version_ofs - imgext_ofs) return -1; + /* try finding the optional header */ + if (stream->fh.length < len + 256) return len; + FS_fseek(&stream->fh, -(len + 256), SEEK_END); + FS_fread(buf, 1, 256, &stream->fh); + /* [0..9]: sync string, [30..255]: 0x20 */ + if (memcmp(buf, syncstr, 10) != 0) { + return len; + } + #ifdef MMTAG_PARANOID + for (j = 30; j < 256; ++j) { + if (buf[j] != ' ') return len; + } + #endif + return len + 256; /* header is present. */ +} + +static int probe_id3v1(snd_stream_t *stream, unsigned char *buf, int atend) { + if (stream->fh.length >= 128) { + FS_fseek(&stream->fh, -128, SEEK_END); + if (FS_fread(buf, 1, 128, &stream->fh) != 128) + return -1; + if (is_id3v1(buf, 128)) { + if (!atend) { /* possible false positive? */ + if (is_musicmatch(buf + 128 - 48, 48) || + is_apetag (buf + 128 - 32, 32) || + is_lyrics3tag(buf + 128 - 15, 15)) { + return 0; + } + } + stream->fh.length -= 128; + Con_DPrintf("MP3: skipped %ld bytes ID3v1 tag\n", 128L); + return 1; + /* FIXME: handle possible double-ID3v1 tags? */ + } + } + return 0; +} +static int probe_mmtag(snd_stream_t *stream, unsigned char *buf) { + long len; + if (stream->fh.length >= 68) { + FS_fseek(&stream->fh, -48, SEEK_END); + if (FS_fread(buf, 1, 48, &stream->fh) != 48) + return -1; + if (is_musicmatch(buf, 48)) { + len = get_musicmatch_len(stream); + if (len < 0) return -1; + if (len >= stream->fh.length) return -1; + stream->fh.length -= len; + Con_DPrintf("MP3: skipped %ld bytes MusicMatch tag\n", len); + return 1; + } + } + return 0; +} +static int probe_apetag(snd_stream_t *stream, unsigned char *buf) { + long len; + if (stream->fh.length >= 32) { + FS_fseek(&stream->fh, -32, SEEK_END); + if (FS_fread(buf, 1, 32, &stream->fh) != 32) + return -1; + if (is_apetag(buf, 32)) { + len = get_ape_len(buf); + if (len >= stream->fh.length) return -1; + stream->fh.length -= len; + Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len); + return 1; + } + } + return 0; +} +static int probe_lyrics3(snd_stream_t *stream, unsigned char *buf) { + long len; + if (stream->fh.length >= 15) { + FS_fseek(&stream->fh, -15, SEEK_END); + if (FS_fread(buf, 1, 15, &stream->fh) != 15) + return -1; + len = is_lyrics3tag(buf, 15); + if (len == 2) { + len = get_lyrics3v2_len(buf, 6); + if (len >= stream->fh.length) return -1; + if (len < 15) return -1; + FS_fseek(&stream->fh, -len, SEEK_END); + if (FS_fread(buf, 1, 11, &stream->fh) != 11) + return -1; + if (!verify_lyrics3v2(buf, 11)) return -1; + stream->fh.length -= len; + Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len); + return 1; + } + else if (len == 1) { + len = get_lyrics3v1_len(stream); + if (len < 0) return -1; + stream->fh.length -= len; + Con_DPrintf("MP3: skipped %ld bytes Lyrics3 tag\n", len); + return 1; + } + } + return 0; +} + +int mp3_skiptags(snd_stream_t *stream) +{ + unsigned char buf[128]; + long len; size_t readsize; + int c_id3, c_ape, c_lyr, c_mm; + int rc = -1; + /* failsafe */ + long oldlength = stream->fh.length; + long oldstart = stream->fh.start; + + /* MP3 standard has no metadata format, so everyone invented + * their own thing, even with extensions, until ID3v2 became + * dominant: Hence the impossible mess here. + * + * Note: I don't yet care about freaky broken mp3 files with + * double tags. -- O.S. + */ + + readsize = FS_fread(buf, 1, 128, &stream->fh); + if (!readsize || FS_ferror(&stream->fh)) goto fail; + + /* ID3v2 tag is at the start */ + if (is_id3v2(buf, readsize)) { + len = get_id3v2_len(buf, (long)readsize); + if (len >= stream->fh.length) goto fail; + stream->fh.start += len; + stream->fh.length -= len; + Con_DPrintf("MP3: skipped %ld bytes ID3v2 tag\n", len); + } + /* APE tag _might_ be at the start (discouraged + * but not forbidden, either.) read the header. */ + else if (is_apetag(buf, readsize)) { + len = get_ape_len(buf); + if (len >= stream->fh.length) goto fail; + stream->fh.start += len; + stream->fh.length -= len; + Con_DPrintf("MP3: skipped %ld bytes APE tag\n", len); + } + + /* it's not impossible that _old_ MusicMatch tag + * placing itself after ID3v1. */ + if ((c_mm = probe_mmtag(stream, buf)) < 0) { + goto fail; + } + /* ID3v1 tag is at the end */ + if ((c_id3 = probe_id3v1(stream, buf, !c_mm)) < 0) { + goto fail; + } + /* we do not know the order of ape or lyrics3 + * or musicmatch tags, hence the loop here.. */ + c_ape = 0; + c_lyr = 0; + for (;;) { + if (!c_lyr) { + /* care about mp3s with double Lyrics3 tags? */ + if ((c_lyr = probe_lyrics3(stream, buf)) < 0) + goto fail; + if (c_lyr) continue; + } + if (!c_mm) { + if ((c_mm = probe_mmtag(stream, buf)) < 0) + goto fail; + if (c_mm) continue; + } + if (!c_ape) { + if ((c_ape = probe_apetag(stream, buf)) < 0) + goto fail; + if (c_ape) continue; + } + break; + } /* for (;;) */ + + rc = (stream->fh.length > 0)? 0 : -1; + fail: + if (rc < 0) { + stream->fh.start = oldstart; + stream->fh.length = oldlength; + } + FS_rewind(&stream->fh); + return rc; +} +#endif /* USE_CODEC_MP3 */ diff --git a/Quake/snd_mpg123.cpp b/Quake/snd_mpg123.cpp new file mode 100644 index 0000000..801feb8 --- /dev/null +++ b/Quake/snd_mpg123.cpp @@ -0,0 +1,223 @@ +/* MP3 decoding support using libmpg123 + * Copyright (C) 2011-2019 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_MP3) +#include "snd_codec.hpp" +#include "snd_codeci.h" +#include "snd_mp3.h" +#include + +#define MPG123_DEF_SSIZE_T /* we do define ssize_t in our stdinc.h */ +#include + +#if !defined(MPG123_API_VERSION) || (MPG123_API_VERSION < 24) +#error minimum required libmpg123 version is 1.12.0 (api version 24) +#endif + +/* Private data */ +typedef struct _mp3_priv_t +{ + mpg123_handle* handle; + int handle_open; +} mp3_priv_t; + +/* CALLBACKS: libmpg123 expects POSIX read/lseek() behavior! */ +static ssize_t mp3_read (void *f, void *buf, size_t size) +{ + ssize_t ret = (ssize_t) FS_fread(buf, 1, size, (fshandle_t *)f); + if (ret == 0 && errno != 0) + return -1; + return ret; +} +static off_t mp3_seek (void *f, off_t offset, int whence) +{ + if (f == NULL) return -1; + if (FS_fseek((fshandle_t *)f, (long) offset, whence) < 0) + return (off_t)-1; + return (off_t) FS_ftell((fshandle_t *)f); +} + +static qboolean S_MP3_CodecInitialize (void) +{ + if (!mp3_codec.initialized) + { + if (mpg123_init() != MPG123_OK) + { + Con_Printf ("Could not initialize mpg123\n"); + return false; + } + mp3_codec.initialized = true; + } + return true; +} + +static void S_MP3_CodecShutdown (void) +{ + if (mp3_codec.initialized) + { + mp3_codec.initialized = false; + mpg123_exit(); + } +} + +static qboolean S_MP3_CodecOpenStream (snd_stream_t *stream) +{ + long rate = 0; + int encoding = 0, channels = 0; + mp3_priv_t *priv = NULL; + + if (mp3_skiptags(stream) < 0) + { + Con_Printf("Corrupt mp3 file (bad tags.)\n"); + return false; + } + + stream->priv = Z_Malloc(sizeof(mp3_priv_t)); + priv = (mp3_priv_t *) stream->priv; + priv->handle = mpg123_new(NULL, NULL); + if (priv->handle == NULL) + { + Con_Printf("Unable to allocate mpg123 handle\n"); + goto _fail; + } + + if (mpg123_replace_reader_handle(priv->handle, mp3_read, mp3_seek, NULL) != MPG123_OK || + mpg123_open_handle(priv->handle, &stream->fh) != MPG123_OK) + { + Con_Printf("Unable to open mpg123 handle\n"); + goto _fail; + } + priv->handle_open = 1; + + if (mpg123_getformat(priv->handle, &rate, &channels, &encoding) != MPG123_OK) + { + Con_Printf("Unable to retrieve mpg123 format for %s\n", stream->name); + goto _fail; + } + + switch (channels) { + case MPG123_MONO: + stream->info.channels = 1; + break; + case MPG123_STEREO: + stream->info.channels = 2; + break; + default: + Con_Printf("Unsupported number of channels %d in %s\n", channels, stream->name); + goto _fail; + } + + stream->info.rate = rate; + + switch (encoding) { + case MPG123_ENC_UNSIGNED_8: + stream->info.bits = 8; + stream->info.width = 1; + break; + case MPG123_ENC_SIGNED_8: + /* unsupported: force mpg123 to convert */ + stream->info.bits = 8; + stream->info.width = 1; + encoding = MPG123_ENC_UNSIGNED_8; + break; + case MPG123_ENC_SIGNED_16: + stream->info.bits = 16; + stream->info.width = 2; + break; + case MPG123_ENC_UNSIGNED_16: + default: + /* unsupported: force mpg123 to convert */ + stream->info.bits = 16; + stream->info.width = 2; + encoding = MPG123_ENC_SIGNED_16; + break; + } + if (mpg123_format_support(priv->handle, rate, encoding) == 0) + { + Con_Printf("Unsupported format for %s\n", stream->name); + goto _fail; + } + mpg123_format_none(priv->handle); + mpg123_format(priv->handle, rate, channels, encoding); + + return true; +_fail: + if (priv) + { + if (priv->handle) + { + if (priv->handle_open) + mpg123_close(priv->handle); + mpg123_delete(priv->handle); + } + Z_Free(stream->priv); + } + return false; +} + +static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + mp3_priv_t *priv = (mp3_priv_t *) stream->priv; + size_t bytes_read = 0; + int res = mpg123_read (priv->handle, (unsigned char *)buffer, (size_t)bytes, &bytes_read); + switch (res) { + case MPG123_DONE: + Con_DPrintf("mp3 EOF\n"); + case MPG123_OK: + return (int)bytes_read; + } + return -1; /* error */ +} + +static void S_MP3_CodecCloseStream (snd_stream_t *stream) +{ + mp3_priv_t *priv = (mp3_priv_t *) stream->priv; + mpg123_close(priv->handle); + mpg123_delete(priv->handle); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MP3_CodecRewindStream (snd_stream_t *stream) +{ + mp3_priv_t *priv = (mp3_priv_t *) stream->priv; + off_t res = mpg123_seek(priv->handle, 0, SEEK_SET); + if (res >= 0) return 0; + return res; +} + +snd_codec_t mp3_codec = +{ + CODECTYPE_MP3, + false, + "mp3", + S_MP3_CodecInitialize, + S_MP3_CodecShutdown, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecRewindStream, + NULL, /* jump */ + S_MP3_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MP3 */ + diff --git a/Quake/snd_opus.cpp b/Quake/snd_opus.cpp new file mode 100644 index 0000000..fbea338 --- /dev/null +++ b/Quake/snd_opus.cpp @@ -0,0 +1,211 @@ +/* + * Ogg/Opus streaming music support, loosely based on several open source + * Quake engine based projects with many modifications. + * + * Copyright (C) 2012-2013 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_OPUS) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_opus.h" + +#include +#include + + +/* CALLBACK FUNCTIONS: */ + +static int opc_fclose (void *f) +{ + return 0; /* we fclose() elsewhere. */ +} + +static int opc_fread (void *f, unsigned char *buf, int size) +{ + int ret; + + if (size < 0) + { + errno = EINVAL; + return -1; + } + + ret = (int) FS_fread(buf, 1, (size_t)size, (fshandle_t *)f); + if (ret == 0 && errno != 0) + ret = -1; + return ret; +} + +static int opc_fseek (void *f, opus_int64 off, int whence) +{ + if (f == NULL) return (-1); + return FS_fseek((fshandle_t *)f, (long) off, whence); +} + +static opus_int64 opc_ftell (void *f) +{ + return (opus_int64) FS_ftell((fshandle_t *)f); +} + +static const OpusFileCallbacks opc_qfs = +{ + (int (*)(void *, unsigned char *, int)) opc_fread, + (int (*)(void *, opus_int64, int)) opc_fseek, + (opus_int64 (*)(void *)) opc_ftell, + (int (*)(void *)) opc_fclose +}; + +static qboolean S_OPUS_CodecInitialize (void) +{ + return true; +} + +static void S_OPUS_CodecShutdown (void) +{ +} + +static qboolean S_OPUS_CodecOpenStream (snd_stream_t *stream) +{ + OggOpusFile *opFile; + const OpusHead *op_info; + long numstreams; + int res; + + opFile = op_open_callbacks(&stream->fh, &opc_qfs, NULL, 0, &res); + if (!opFile) + { + Con_Printf("%s is not a valid Opus file (error %i).\n", + stream->name, res); + goto _fail; + } + + stream->priv = opFile; + + if (!op_seekable(opFile)) + { + Con_Printf("Opus stream %s not seekable.\n", stream->name); + goto _fail; + } + + op_info = op_head(opFile, -1); + if (!op_info) + { + Con_Printf("Unable to get stream information for %s.\n", stream->name); + goto _fail; + } + + /* FIXME: handle section changes */ + numstreams = op_info->stream_count; + if (numstreams != 1) + { + Con_Printf("More than one (%ld) stream in %s\n", + (long)op_info->stream_count, stream->name); + goto _fail; + } + + if (op_info->channel_count != 1 && op_info->channel_count != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + op_info->channel_count, stream->name); + goto _fail; + } + + /* All Opus audio is coded at 48 kHz, and should also be decoded + * at 48 kHz for playback: info->input_sample_rate only tells us + * the sampling rate of the original input before opus encoding. + * S_RawSamples() shall already downsample this, as necessary. */ + stream->info.rate = 48000; + stream->info.channels = op_info->channel_count; + /* op_read() yields 16-bit output using native endian ordering: */ + stream->info.bits = 16; + stream->info.width = 2; + + return true; +_fail: + if (opFile) + op_free(opFile); + return false; +} + +static int S_OPUS_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int section; /* FIXME: handle section changes */ + int cnt, res, rem; + opus_int16 * ptr; + + rem = bytes / stream->info.width; + if (rem / stream->info.channels <= 0) + return 0; + + cnt = 0; + ptr = (opus_int16 *) buffer; + while (1) + { + /* op_read() yields 16-bit output using native endian ordering. returns + * the number of samples read per channel on success, or a negative value + * on failure. */ + res = op_read((OggOpusFile *)stream->priv, ptr, rem, §ion); + if (res <= 0) + break; + cnt += res; + res *= stream->info.channels; + rem -= res; + if (rem <= 0) + break; + ptr += res; + } + + if (res < 0) + return res; + + cnt *= (stream->info.channels * stream->info.width); + return cnt; +} + +static void S_OPUS_CodecCloseStream (snd_stream_t *stream) +{ + op_free((OggOpusFile *)stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_OPUS_CodecRewindStream (snd_stream_t *stream) +{ + return op_pcm_seek ((OggOpusFile *)stream->priv, 0); +} + +snd_codec_t opus_codec = +{ + CODECTYPE_OPUS, + true, /* always available. */ + "opus", + S_OPUS_CodecInitialize, + S_OPUS_CodecShutdown, + S_OPUS_CodecOpenStream, + S_OPUS_CodecReadStream, + S_OPUS_CodecRewindStream, + NULL, /* jump */ + S_OPUS_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_OPUS */ + diff --git a/Quake/snd_opus.hpp b/Quake/snd_opus.hpp new file mode 100644 index 0000000..4c2a25f --- /dev/null +++ b/Quake/snd_opus.hpp @@ -0,0 +1,13 @@ +/* Ogg/Opus streaming music support. */ + +#if !defined(_SND_OPUS_H_) +#define _SND_OPUS_H_ 1 + +#if defined(USE_CODEC_OPUS) + +extern snd_codec_t opus_codec; + +#endif /* USE_CODEC_OPUS */ + +#endif /* ! _SND_OPUS_H_ */ + diff --git a/Quake/snd_sdl.cpp b/Quake/snd_sdl.cpp new file mode 100644 index 0000000..d8d06b2 --- /dev/null +++ b/Quake/snd_sdl.cpp @@ -0,0 +1,212 @@ +/* + * snd_sdl.c - SDL audio driver for Hexen II: Hammer of Thyrion (uHexen2) + * based on implementations found in the quakeforge and ioquake3 projects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005-2012 O.Sezer + * Copyright (C) 2010-2014 QuakeSpasm developers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + +static int buffersize; + + +static void SDLCALL paint_audio (void *unused, Uint8 *stream, int len) +{ + int pos, tobufend; + int len1, len2; + + if (!shm) + { /* shouldn't happen, but just in case */ + memset(stream, 0, len); + return; + } + + pos = (shm->samplepos * (shm->samplebits / 8)); + if (pos >= buffersize) + shm->samplepos = pos = 0; + + tobufend = buffersize - pos; /* bytes to buffer's end. */ + len1 = len; + len2 = 0; + + if (len1 > tobufend) + { + len1 = tobufend; + len2 = len - len1; + } + + memcpy(stream, shm->buffer + pos, len1); + + if (len2 <= 0) + { + shm->samplepos += (len1 / (shm->samplebits / 8)); + } + else + { /* wraparound? */ + memcpy(stream + len1, shm->buffer, len2); + shm->samplepos = (len2 / (shm->samplebits / 8)); + } + + if (shm->samplepos >= buffersize) + shm->samplepos = 0; +} + +qboolean SNDDMA_Init (dma_t *dma) +{ + SDL_AudioSpec desired; + int tmp, val; + char drivername[128]; + + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + Con_Printf("Couldn't init SDL audio: %s\n", SDL_GetError()); + return false; + } + + /* Set up the desired format */ + desired.freq = snd_mixspeed.value; + desired.format = (loadas8bit.value) ? AUDIO_U8 : AUDIO_S16SYS; + desired.channels = 2; /* = desired_channels; */ + if (desired.freq <= 11025) + desired.samples = 256; + else if (desired.freq <= 22050) + desired.samples = 512; + else if (desired.freq <= 44100) + desired.samples = 1024; + else if (desired.freq <= 56000) + desired.samples = 2048; /* for 48 kHz */ + else + desired.samples = 4096; /* for 96 kHz */ + desired.callback = paint_audio; + desired.userdata = NULL; + + /* Open the audio device */ + if (SDL_OpenAudio(&desired, NULL) == -1) + { + Con_Printf("Couldn't open SDL audio: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + memset ((void *) dma, 0, sizeof(dma_t)); + shm = dma; + + /* Fill the audio DMA information block */ + /* Since we passed NULL as the 'obtained' spec to SDL_OpenAudio(), + * SDL will convert to hardware format for us if needed, hence we + * directly use the desired values here. */ + shm->samplebits = (desired.format & 0xFF); /* first byte of format is bits */ + shm->signed8 = (desired.format == AUDIO_S8); + shm->speed = desired.freq; + shm->channels = desired.channels; + tmp = (desired.samples * desired.channels) * 10; + if (tmp & (tmp - 1)) + { /* make it a power of two */ + val = 1; + while (val < tmp) + val <<= 1; + + tmp = val; + } + shm->samples = tmp; + shm->samplepos = 0; + shm->submission_chunk = 1; + + Con_Printf ("SDL audio spec : %d Hz, %d samples, %d channels\n", + desired.freq, desired.samples, desired.channels); +#if defined(USE_SDL2) + { + const char *driver = SDL_GetCurrentAudioDriver(); + const char *device = SDL_GetAudioDeviceName(0, SDL_FALSE); + q_snprintf(drivername, sizeof(drivername), "%s - %s", + driver != NULL ? driver : "(UNKNOWN)", + device != NULL ? device : "(UNKNOWN)"); + } +#else + if (SDL_AudioDriverName(drivername, sizeof(drivername)) == NULL) + strcpy(drivername, "(UNKNOWN)"); +#endif + buffersize = shm->samples * (shm->samplebits / 8); + Con_Printf ("SDL audio driver: %s, %d bytes buffer\n", drivername, buffersize); + + shm->buffer = (unsigned char *) calloc (1, buffersize); + if (!shm->buffer) + { + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + shm = NULL; + Con_Printf ("Failed allocating memory for SDL audio\n"); + return false; + } + + SDL_PauseAudio(0); + + return true; +} + +int SNDDMA_GetDMAPos (void) +{ + return shm->samplepos; +} + +void SNDDMA_Shutdown (void) +{ + if (shm) + { + Con_Printf ("Shutting down SDL sound\n"); + SDL_CloseAudio(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + if (shm->buffer) + free (shm->buffer); + shm->buffer = NULL; + shm = NULL; + } +} + +void SNDDMA_LockBuffer (void) +{ + SDL_LockAudio (); +} + +void SNDDMA_Submit (void) +{ + SDL_UnlockAudio(); +} + +void SNDDMA_BlockSound (void) +{ + SDL_PauseAudio(1); +} + +void SNDDMA_UnblockSound (void) +{ + SDL_PauseAudio(0); +} + diff --git a/Quake/snd_umx.cpp b/Quake/snd_umx.cpp new file mode 100644 index 0000000..50f3704 --- /dev/null +++ b/Quake/snd_umx.cpp @@ -0,0 +1,413 @@ +/** + * Unreal UMX container support. + * UPKG parsing partially based on Unreal Media Ripper (UMR) v0.3 + * by Andy Ward , with additional updates + * by O. Sezer - see git repo at https://github.com/sezero/umr.git + * + * Copyright (C) 2013-2021 O. Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_UMX) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_umx.h" + +typedef int32_t fci_t; /* FCompactIndex */ + +#define UPKG_HDR_TAG 0x9e2a83c1 + +struct _genhist { /* for upkg versions >= 68 */ + int32_t export_count; + int32_t name_count; +}; + +struct upkg_hdr { + uint32_t tag; /* UPKG_HDR_TAG */ + int32_t file_version; + uint32_t pkg_flags; + int32_t name_count; /* number of names in name table (>= 0) */ + int32_t name_offset; /* offset to name table (>= 0) */ + int32_t export_count; /* num. exports in export table (>= 0) */ + int32_t export_offset; /* offset to export table (>= 0) */ + int32_t import_count; /* num. imports in export table (>= 0) */ + int32_t import_offset; /* offset to import table (>= 0) */ + + /* number of GUIDs in heritage table (>= 1) and table's offset: + * only with versions < 68. */ + int32_t heritage_count; + int32_t heritage_offset; + /* with versions >= 68: a GUID, a dword for generation count + * and export_count and name_count dwords for each generation: */ + uint32_t guid[4]; + int32_t generation_count; +#define UPKG_HDR_SIZE 64 /* 64 bytes up until here */ + struct _genhist *gen; +}; +COMPILE_TIME_ASSERT(upkg_hdr, offsetof(struct upkg_hdr, gen) == UPKG_HDR_SIZE); + +#define UMUSIC_IT 0 +#define UMUSIC_S3M 1 +#define UMUSIC_XM 2 +#define UMUSIC_MOD 3 +#define UMUSIC_WAV 4 +#define UMUSIC_MP2 5 + +static const char *mustype[] = { + "IT", "S3M", "XM", "MOD", + "WAV", "MP2", NULL +}; + +/* decode an FCompactIndex. + * original documentation by Tim Sweeney was at + * http://unreal.epicgames.com/Packages.htm + * also see Unreal Wiki: + * http://wiki.beyondunreal.com/Legacy:Package_File_Format/Data_Details + */ +static fci_t get_fci (const char *in, int *pos) +{ + int32_t a; + int size; + + size = 1; + a = in[0] & 0x3f; + + if (in[0] & 0x40) { + size++; + a |= (in[1] & 0x7f) << 6; + + if (in[1] & 0x80) { + size++; + a |= (in[2] & 0x7f) << 13; + + if (in[2] & 0x80) { + size++; + a |= (in[3] & 0x7f) << 20; + + if (in[3] & 0x80) { + size++; + a |= (in[4] & 0x3f) << 27; + } + } + } + } + + if (in[0] & 0x80) + a = -a; + + *pos += size; + + return a; +} + +static int get_objtype (fshandle_t *f, int32_t ofs, int type) +{ + char sig[16]; +_retry: + memset(sig, 0, sizeof(sig)); + FS_fseek(f, ofs, SEEK_SET); + FS_fread(sig, 16, 1, f); + if (type == UMUSIC_IT) { + if (memcmp(sig, "IMPM", 4) == 0) + return UMUSIC_IT; + return -1; + } + if (type == UMUSIC_XM) { + if (memcmp(sig, "Extended Module:", 16) != 0) + return -1; + FS_fread(sig, 16, 1, f); + if (sig[0] != ' ') return -1; + FS_fread(sig, 16, 1, f); + if (sig[5] != 0x1a) return -1; + return UMUSIC_XM; + } + if (type == UMUSIC_MP2) { + unsigned char *p = (unsigned char *)sig; + uint16_t u = ((p[0] << 8) | p[1]) & 0xFFFE; + if (u == 0xFFFC || u == 0xFFF4) + return UMUSIC_MP2; + return -1; + } + if (type == UMUSIC_WAV) { + if (memcmp(sig, "RIFF", 4) == 0 && memcmp(&sig[8], "WAVE", 4) == 0) + return UMUSIC_WAV; + return -1; + } + + FS_fseek(f, ofs + 44, SEEK_SET); + FS_fread(sig, 4, 1, f); + if (type == UMUSIC_S3M) { + if (memcmp(sig, "SCRM", 4) == 0) + return UMUSIC_S3M; + /*return -1;*/ + /* SpaceMarines.umx and Starseek.umx from Return to NaPali + * report as "s3m" whereas the actual music format is "it" */ + type = UMUSIC_IT; + goto _retry; + } + + FS_fseek(f, ofs + 1080, SEEK_SET); + FS_fread(sig, 4, 1, f); + if (type == UMUSIC_MOD) { + if (memcmp(sig, "M.K.", 4) == 0 || memcmp(sig, "M!K!", 4) == 0) + return UMUSIC_MOD; + return -1; + } + + return -1; +} + +static int read_export (fshandle_t *f, const struct upkg_hdr *hdr, + int32_t *ofs, int32_t *objsize) +{ + char buf[40]; + int idx = 0, t; + + FS_fseek(f, *ofs, SEEK_SET); + if (FS_fread(buf, 4, 10, f) < 10) + return -1; + + if (hdr->file_version < 40) idx += 8; /* 00 00 00 00 00 00 00 00 */ + if (hdr->file_version < 60) idx += 16; /* 81 00 00 00 00 00 FF FF FF FF FF FF FF FF 00 00 */ + get_fci(&buf[idx], &idx); /* skip junk */ + t = get_fci(&buf[idx], &idx); /* type_name */ + if (hdr->file_version > 61) idx += 4; /* skip export size */ + *objsize = get_fci(&buf[idx], &idx); + *ofs += idx; /* offset for real data */ + + return t; /* return type_name index */ +} + +static int read_typname(fshandle_t *f, const struct upkg_hdr *hdr, + int idx, char *out) +{ + int i, s; + long l; + char buf[64]; + + if (idx >= hdr->name_count) return -1; + memset(buf, 0, 64); + for (i = 0, l = 0; i <= idx; i++) { + if (FS_fseek(f, hdr->name_offset + l, SEEK_SET) < 0) return -1; + if (!FS_fread(buf, 1, 63, f)) return -1; + if (hdr->file_version >= 64) { + s = *(signed char *)buf; /* numchars *including* terminator */ + if (s <= 0) return -1; + l += s + 5; /* 1 for buf[0], 4 for int32_t name_flags */ + } else { + l += (long)strlen(buf); + l += 5; /* 1 for terminator, 4 for int32_t name_flags */ + } + } + + strcpy(out, (hdr->file_version >= 64)? &buf[1] : buf); + return 0; +} + +static int probe_umx (fshandle_t *f, const struct upkg_hdr *hdr, + int32_t *ofs, int32_t *objsize) +{ + int i, idx, t; + int32_t s, pos; + long fsiz; + char buf[64]; + + idx = 0; + fsiz = FS_filelength (f); + + if (hdr->name_offset >= fsiz || + hdr->export_offset >= fsiz || + hdr->import_offset >= fsiz) { + Con_DPrintf("Illegal values in header.\n"); + return -1; + } + + /* Find the offset and size of the first IT, S3M or XM + * by parsing the exports table. The umx files should + * have only one export. Kran32.umx from Unreal has two, + * but both pointing to the same music. */ + if (hdr->export_offset >= fsiz) return -1; + memset(buf, 0, 64); + FS_fseek(f, hdr->export_offset, SEEK_SET); + FS_fread(buf, 1, 64, f); + + get_fci(&buf[idx], &idx); /* skip class_index */ + get_fci(&buf[idx], &idx); /* skip super_index */ + if (hdr->file_version >= 60) idx += 4; /* skip int32 package_index */ + get_fci(&buf[idx], &idx); /* skip object_name */ + idx += 4; /* skip int32 object_flags */ + + s = get_fci(&buf[idx], &idx); /* get serial_size */ + if (s <= 0) return -1; + pos = get_fci(&buf[idx],&idx); /* get serial_offset */ + if (pos < 0 || pos > fsiz - 40) return -1; + + if ((t = read_export(f, hdr, &pos, &s)) < 0) return -1; + if (s <= 0 || s > fsiz - pos) return -1; + + if (read_typname(f, hdr, t, buf) < 0) return -1; + for (i = 0; mustype[i] != NULL; i++) { + if (!q_strcasecmp(buf, mustype[i])) { + t = i; + break; + } + } + if (mustype[i] == NULL) return -1; + if ((t = get_objtype(f, pos, t)) < 0) return -1; + + *ofs = pos; + *objsize = s; + return t; +} + +static int32_t probe_header (fshandle_t *f, struct upkg_hdr *hdr) +{ + if (FS_fread(hdr, 1, UPKG_HDR_SIZE, f) < UPKG_HDR_SIZE) + return -1; + /* byte swap the header - all members are 32 bit LE values */ + hdr->tag = (uint32_t) LittleLong(hdr->tag); + hdr->file_version = LittleLong(hdr->file_version); + hdr->pkg_flags = (uint32_t) LittleLong(hdr->pkg_flags); + hdr->name_count = LittleLong(hdr->name_count); + hdr->name_offset = LittleLong(hdr->name_offset); + hdr->export_count = LittleLong(hdr->export_count); + hdr->export_offset = LittleLong(hdr->export_offset); + hdr->import_count = LittleLong(hdr->import_count); + hdr->import_offset = LittleLong(hdr->import_offset); + + if (hdr->tag != UPKG_HDR_TAG) { + Con_DPrintf("Unknown header tag 0x%x\n", hdr->tag); + return -1; + } + if (hdr->name_count < 0 || + hdr->export_count < 0 || + hdr->import_count < 0 || + hdr->name_offset < 36 || + hdr->export_offset < 36 || + hdr->import_offset < 36) { + Con_DPrintf("Illegal values in header.\n"); + return -1; + } + +#if 1 /* no need being overzealous */ + return 0; +#else + switch (hdr->file_version) { + case 35: case 37: /* Unreal beta - */ + case 40: case 41: /* 1998 */ + case 61:/* Unreal */ + case 62:/* Unreal Tournament */ + case 63:/* Return to NaPali */ + case 64:/* Unreal Tournament */ + case 66:/* Unreal Tournament */ + case 68:/* Unreal Tournament */ + case 69:/* Tactical Ops */ + case 75:/* Harry Potter and the Philosopher's Stone */ + case 76: /* mpeg layer II data */ + case 83:/* Mobile Forces */ + return 0; + } + + Con_DPrintf("Unknown upkg version %d\n", hdr->file_version); + return -1; +#endif /* #if 0 */ +} + +static int process_upkg (fshandle_t *f, int32_t *ofs, int32_t *objsize) +{ + struct upkg_hdr header; + + memset(&header, 0, sizeof(header)); + if (probe_header(f, &header) < 0) + return -1; + + return probe_umx(f, &header, ofs, objsize); +} + +static qboolean S_UMX_CodecInitialize (void) +{ + return true; +} + +static void S_UMX_CodecShutdown (void) +{ +} + +static qboolean S_UMX_CodecOpenStream (snd_stream_t *stream) +{ + int type; + int32_t ofs = 0, size = 0; + + type = process_upkg(&stream->fh, &ofs, &size); + if (type < 0) { + Con_DPrintf("%s: unrecognized umx\n", stream->name); + return false; + } + + Con_DPrintf("%s: %s data @ 0x%x, %d bytes\n", stream->name, mustype[type], ofs, size); + /* hack the fshandle_t start pos and length members so + * that only the relevant data is accessed from now on */ + stream->fh.start += ofs; + stream->fh.length = size; + FS_fseek(&stream->fh, 0, SEEK_SET); + + switch (type) { + case UMUSIC_IT: + case UMUSIC_S3M: + case UMUSIC_XM: + case UMUSIC_MOD: return S_CodecForwardStream(stream, CODECTYPE_MOD); + case UMUSIC_WAV: return S_CodecForwardStream(stream, CODECTYPE_WAV); + case UMUSIC_MP2: return S_CodecForwardStream(stream, CODECTYPE_MP3); + } + + return false; +} + +static int S_UMX_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + return -1; +} + +static void S_UMX_CodecCloseStream (snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +static int S_UMX_CodecRewindStream (snd_stream_t *stream) +{ + return -1; +} + +snd_codec_t umx_codec = +{ + CODECTYPE_UMX, + true, /* always available. */ + "umx", + S_UMX_CodecInitialize, + S_UMX_CodecShutdown, + S_UMX_CodecOpenStream, + S_UMX_CodecReadStream, + S_UMX_CodecRewindStream, + NULL, /* jump */ + S_UMX_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_UMX */ + diff --git a/Quake/snd_umx.hpp b/Quake/snd_umx.hpp new file mode 100644 index 0000000..da3107d --- /dev/null +++ b/Quake/snd_umx.hpp @@ -0,0 +1,12 @@ +/* Unreal UMX format support */ +#if !defined(_SND_UMX_H_) +#define _SND_UMX_H_ + +#if defined(USE_CODEC_UMX) + +extern snd_codec_t umx_codec; + +#endif /* USE_CODEC_UMX */ + +#endif /* ! _SND_UMX_H_ */ + diff --git a/Quake/snd_vorbis.cpp b/Quake/snd_vorbis.cpp new file mode 100644 index 0000000..d0853e1 --- /dev/null +++ b/Quake/snd_vorbis.cpp @@ -0,0 +1,205 @@ +/* + * Ogg/Vorbis streaming music support, loosely based on several open source + * Quake engine based projects with many modifications. + * + * Copyright (C) 2010-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_VORBIS) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_vorbis.h" + +#define OV_EXCLUDE_STATIC_CALLBACKS +#if defined(VORBIS_USE_TREMOR) +/* for Tremor / Vorbisfile api differences, + * see doc/diff.html in the Tremor package. */ +#include +#else +#include +#endif + +/* Vorbis codec can return the samples in a number of different + * formats, we use the standard signed short format. */ +#define VORBIS_SAMPLEBITS 16 +#define VORBIS_SAMPLEWIDTH 2 +#define VORBIS_SIGNED_DATA 1 + +/* CALLBACK FUNCTIONS: */ + +static int ovc_fclose (void *f) +{ + return 0; /* we fclose() elsewhere. */ +} + +static int ovc_fseek (void *f, ogg_int64_t off, int whence) +{ + if (f == NULL) return (-1); + return FS_fseek((fshandle_t *)f, (long) off, whence); +} + +static ov_callbacks ovc_qfs = +{ + (size_t (*)(void *, size_t, size_t, void *)) FS_fread, + (int (*)(void *, ogg_int64_t, int)) ovc_fseek, + (int (*)(void *)) ovc_fclose, + (long (*)(void *)) FS_ftell +}; + +static qboolean S_VORBIS_CodecInitialize (void) +{ + return true; +} + +static void S_VORBIS_CodecShutdown (void) +{ +} + +static qboolean S_VORBIS_CodecOpenStream (snd_stream_t *stream) +{ + OggVorbis_File *ovFile; + vorbis_info *ovf_info; + long numstreams; + int res; + + ovFile = (OggVorbis_File *) Z_Malloc(sizeof(OggVorbis_File)); + stream->priv = ovFile; + res = ov_open_callbacks(&stream->fh, ovFile, NULL, 0, ovc_qfs); + if (res != 0) + { + Con_Printf("%s is not a valid Ogg Vorbis file (error %i).\n", + stream->name, res); + goto _fail; + } + + if (!ov_seekable(ovFile)) + { + Con_Printf("Stream %s not seekable.\n", stream->name); + goto _fail; + } + + ovf_info = ov_info(ovFile, 0); + if (!ovf_info) + { + Con_Printf("Unable to get stream info for %s.\n", stream->name); + goto _fail; + } + + /* FIXME: handle section changes */ + numstreams = ov_streams(ovFile); + if (numstreams != 1) + { + Con_Printf("More than one (%ld) stream in %s.\n", + numstreams, stream->name); + goto _fail; + } + + if (ovf_info->channels != 1 && ovf_info->channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + ovf_info->channels, stream->name); + goto _fail; + } + + stream->info.rate = ovf_info->rate; + stream->info.channels = ovf_info->channels; + stream->info.bits = VORBIS_SAMPLEBITS; + stream->info.width = VORBIS_SAMPLEWIDTH; + + return true; +_fail: + if (res == 0) + ov_clear(ovFile); + Z_Free(ovFile); + return false; +} + +static int S_VORBIS_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int section; /* FIXME: handle section changes */ + int cnt, res, rem; + char * ptr; + + cnt = 0; rem = bytes; + ptr = (char *) buffer; + while (1) + { + /* # ov_read() from libvorbisfile returns the decoded PCM audio + * in requested endianness, signedness and word size. + * # ov_read() from Tremor (libvorbisidec) returns decoded audio + * always in host-endian, signed 16 bit PCM format. + * # For both of the libraries, if the audio is multichannel, + * the channels are interleaved in the output buffer. + */ + res = ov_read( (OggVorbis_File *)stream->priv, ptr, rem, +#ifndef VORBIS_USE_TREMOR + host_bigendian, + VORBIS_SAMPLEWIDTH, + VORBIS_SIGNED_DATA, +#endif + §ion ); + if (res <= 0) + break; + rem -= res; + cnt += res; + if (rem <= 0) + break; + ptr += res; + } + + if (res < 0) + return res; + return cnt; +} + +static void S_VORBIS_CodecCloseStream (snd_stream_t *stream) +{ + ov_clear((OggVorbis_File *)stream->priv); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_VORBIS_CodecRewindStream (snd_stream_t *stream) +{ +/* for libvorbisfile, the ov_time_seek() position argument + * is seconds as doubles, whereas for Tremor libvorbisidec + * it is milliseconds as 64 bit integers. + */ + return ov_time_seek ((OggVorbis_File *)stream->priv, 0); +} + +snd_codec_t vorbis_codec = +{ + CODECTYPE_VORBIS, + true, /* always available. */ + "ogg", + S_VORBIS_CodecInitialize, + S_VORBIS_CodecShutdown, + S_VORBIS_CodecOpenStream, + S_VORBIS_CodecReadStream, + S_VORBIS_CodecRewindStream, + NULL, /* jump */ + S_VORBIS_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_VORBIS */ + diff --git a/Quake/snd_vorbis.hpp b/Quake/snd_vorbis.hpp new file mode 100644 index 0000000..0f1cc9e --- /dev/null +++ b/Quake/snd_vorbis.hpp @@ -0,0 +1,13 @@ +/* Ogg/Vorbis streaming music support. */ + +#if !defined(_SND_VORBIS_H_) +#define _SND_VORBIS_H_ 1 + +#if defined(USE_CODEC_VORBIS) + +extern snd_codec_t vorbis_codec; + +#endif /* USE_CODEC_VORBIS */ + +#endif /* ! _SND_VORBIS_H_ */ + diff --git a/Quake/snd_wave.cpp b/Quake/snd_wave.cpp new file mode 100644 index 0000000..4855661 --- /dev/null +++ b/Quake/snd_wave.cpp @@ -0,0 +1,275 @@ +/* + * WAV streaming music support. Adapted from ioquake3 with changes. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010-2012 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_WAVE) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_wave.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong (FILE *f) +{ + int v; + if (!fread(&v, sizeof(v), 1, f)) + return -1; + return LittleLong(v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort(FILE *f) +{ + short v; + if (!fread(&v, sizeof(v), 1, f)) + return -1; + return LittleShort(v); +} + +/* +================= +WAV_ReadChunkInfo +================= +*/ +static int WAV_ReadChunkInfo(FILE *f, char *name) +{ + int len; + + name[4] = 0; + + if (!fread(name, 4, 1, f)) + return -1; + + len = FGetLittleLong(f); + if (len < 0) + { + Con_Printf("WAV: Negative chunk length\n"); + return -1; + } + + return len; +} + +/* +================= +WAV_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int WAV_FindRIFFChunk(FILE *f, const char *chunk) +{ + char name[5]; + int len; + + while ((len = WAV_ReadChunkInfo(f, name)) >= 0) + { + /* If this is the right chunk, return */ + if (!strncmp(name, chunk, 4)) + return len; + len = ((len + 1) & ~1); /* pad by 2 . */ + + /* Not the right chunk - skip it */ + fseek(f, len, SEEK_CUR); + } + + return -1; +} + +/* +================= +WAV_ReadRIFFHeader +================= +*/ +static qboolean WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *info) +{ + char dump[16]; + int wav_format; + int fmtlen = 0; + + if (fread(dump, 1, 12, file) < 12 || + strncmp(dump, "RIFF", 4) != 0 || + strncmp(&dump[8], "WAVE", 4) != 0) + { + Con_Printf("%s is missing RIFF/WAVE chunks\n", name); + return false; + } + + /* Scan for the format chunk */ + if ((fmtlen = WAV_FindRIFFChunk(file, "fmt ")) < 0) + { + Con_Printf("%s is missing fmt chunk\n", name); + return false; + } + + /* Save the parameters */ + wav_format = FGetLittleShort(file); + if (wav_format != WAV_FORMAT_PCM) + { + Con_Printf("%s is not Microsoft PCM format\n", name); + return false; + } + + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + info->bits = FGetLittleShort(file); + + if (info->bits != 8 && info->bits != 16) + { + Con_Printf("%s is not 8 or 16 bit\n", name); + return false; + } + + info->width = info->bits / 8; + info->dataofs = 0; + + /* Skip the rest of the format chunk if required */ + if (fmtlen > 16) + { + fmtlen -= 16; + fseek(file, fmtlen, SEEK_CUR); + } + + /* Scan for the data chunk */ + if ((info->size = WAV_FindRIFFChunk(file, "data")) < 0) + { + Con_Printf("%s is missing data chunk\n", name); + return false; + } + + if (info->channels != 1 && info->channels != 2) + { + Con_Printf("Unsupported number of channels %d in %s\n", + info->channels, name); + return false; + } + info->samples = (info->size / info->width) / info->channels; + if (info->samples == 0) + { + Con_Printf("%s has zero samples\n", name); + return false; + } + + return true; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +static qboolean S_WAV_CodecOpenStream(snd_stream_t *stream) +{ + long start = stream->fh.start; + + /* Read the RIFF header */ + /* The file reads are sequential, therefore no need + * for the FS_*() functions: We will manipulate the + * file by ourselves from now on. */ + if (!WAV_ReadRIFFHeader(stream->name, stream->fh.file, &stream->info)) + return false; + + stream->fh.start = ftell(stream->fh.file); /* reset to data position */ + if (stream->fh.start - start + stream->info.size > stream->fh.length) + { + Con_Printf("%s data size mismatch\n", stream->name); + return false; + } + + return true; +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->fh.pos; + int i, samples; + + if (remaining <= 0) + return 0; + if (bytes > remaining) + bytes = remaining; + stream->fh.pos += bytes; + if (!fread(buffer, bytes, 1, stream->fh.file)) + return -1; + if (stream->info.width == 2) + { + samples = bytes / 2; + for (i = 0; i < samples; i++) + ((short *)buffer)[i] = LittleShort( ((short *)buffer)[i] ); + } + return bytes; +} + +static void S_WAV_CodecCloseStream (snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +static int S_WAV_CodecRewindStream (snd_stream_t *stream) +{ + FS_rewind(&stream->fh); + return 0; +} + +static qboolean S_WAV_CodecInitialize (void) +{ + return true; +} + +static void S_WAV_CodecShutdown (void) +{ +} + +snd_codec_t wav_codec = +{ + CODECTYPE_WAVE, + true, /* always available. */ + "wav", + S_WAV_CodecInitialize, + S_WAV_CodecShutdown, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecRewindStream, + NULL, /* jump */ + S_WAV_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_WAVE */ + diff --git a/Quake/snd_wave.hpp b/Quake/snd_wave.hpp new file mode 100644 index 0000000..7f7f50b --- /dev/null +++ b/Quake/snd_wave.hpp @@ -0,0 +1,13 @@ +/* WAV streaming music support. */ + +#if !defined(_SND_WAVE_H_) +#define _SND_WAVE_H_ + +#if defined(USE_CODEC_WAVE) + +extern snd_codec_t wav_codec; + +#endif /* USE_CODEC_WAVE */ + +#endif /* ! _SND_WAVE_H_ */ + diff --git a/Quake/snd_xmp.cpp b/Quake/snd_xmp.cpp new file mode 100644 index 0000000..c8826e0 --- /dev/null +++ b/Quake/snd_xmp.cpp @@ -0,0 +1,187 @@ +/* tracker music (module file) decoding support using libxmp >= v4.2.0 + * https://sourceforge.net/projects/xmp/ + * https://github.com/libxmp/libxmp.git + * + * Copyright (C) 2016 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "quakedef.hpp" + +#if defined(USE_CODEC_XMP) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_xmp.h" +#if defined(_WIN32) && defined(LIBXMP_STATIC) +#define BUILDING_STATIC +#endif +#include +#if ((XMP_VERCODE+0) < 0x040200) +#error libxmp version 4.2 or newer is required +#endif + +static qboolean S_XMP_CodecInitialize (void) +{ + return true; +} + +static void S_XMP_CodecShutdown (void) +{ +} + +#if (XMP_VERCODE >= 0x040500) +static unsigned long xmp_fread(void *dest, unsigned long len, unsigned long nmemb, void *f) +{ + return FS_fread(dest, len, nmemb, (fshandle_t *)f); +} +static int xmp_fseek(void *f, long offset, int whence) +{ + return FS_fseek((fshandle_t *)f, offset, whence); +} +static long xmp_ftell(void *f) +{ + return FS_ftell((fshandle_t *)f); +} +#endif + +static qboolean S_XMP_CodecOpenStream (snd_stream_t *stream) +{ +/* need to load the whole file into memory and pass it to libxmp + * using xmp_load_module_from_memory() which requires libxmp >= 4.2. + * libxmp-4.0/4.1 only have xmp_load_module() which accepts a file + * name which isn't good with files in containers like paks, etc. + * On the other hand, libxmp >= 4.5 introduces file callbacks: use + * if available. */ + xmp_context c; +#if (XMP_VERCODE >= 0x040500) + struct xmp_callbacks file_callbacks = { + xmp_fread, xmp_fseek, xmp_ftell, NULL + }; +#else + byte *moddata; + long len; + int mark; +#endif + int fmt; + + c = xmp_create_context(); + if (c == NULL) + return false; + +#if (XMP_VERCODE >= 0x040500) + if (xmp_load_module_from_callbacks(c, &stream->fh, file_callbacks) < 0) { + Con_DPrintf("Could not load module %s\n", stream->name); + goto err1; + } +#else + len = FS_filelength (&stream->fh); + mark = Hunk_LowMark(); + moddata = (byte *) Hunk_Alloc(len); + FS_fread(moddata, 1, len, &stream->fh); + if (xmp_load_module_from_memory(c, moddata, len) < 0) { + Hunk_FreeToLowMark(mark); + Con_DPrintf("Could not load module %s\n", stream->name); + goto err1; + } + Hunk_FreeToLowMark(mark); /* free original file data */ +#endif + + stream->priv = c; + if (shm->speed > XMP_MAX_SRATE) + stream->info.rate = 44100; + else if (shm->speed < XMP_MIN_SRATE) + stream->info.rate = 11025; + else stream->info.rate = shm->speed; + stream->info.bits = shm->samplebits; + stream->info.width = stream->info.bits / 8; + stream->info.channels = shm->channels; + + fmt = 0; + if (stream->info.channels == 1) + fmt |= XMP_FORMAT_MONO; + if (stream->info.width == 1) + fmt |= XMP_FORMAT_8BIT|XMP_FORMAT_UNSIGNED; + if (xmp_start_player(c, stream->info.rate, fmt) < 0) + goto err2; + + /* interpolation type, default is XMP_INTERP_LINEAR */ + xmp_set_player(c, XMP_PLAYER_INTERP, XMP_INTERP_SPLINE); + + return true; + +err2: xmp_release_module(c); +err1: xmp_free_context(c); + return false; +} + +static int S_XMP_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int r; + /* xmp_play_buffer() requires libxmp >= 4.1. it will write + * native-endian pcm data to the buffer. if the data write + * is partial, the rest of the buffer will be zero-filled. + * the last param is the max number that the current sequence + * of song will be looped, or 0 to disable loop checking. */ + r = xmp_play_buffer((xmp_context)stream->priv, buffer, bytes, !stream->loop); + if (r == 0) { + return bytes; + } + if (r == -XMP_END) { + Con_DPrintf("XMP EOF\n"); + return 0; + } + return -1; +} + +static void S_XMP_CodecCloseStream (snd_stream_t *stream) +{ + xmp_context c = (xmp_context)stream->priv; + xmp_end_player(c); + xmp_release_module(c); + xmp_free_context(c); + S_CodecUtilClose(&stream); +} + +static int S_XMP_CodecJumpToOrder (snd_stream_t *stream, int to) +{ + return xmp_set_position((xmp_context)stream->priv, to); +} + +static int S_XMP_CodecRewindStream (snd_stream_t *stream) +{ + int ret = xmp_seek_time((xmp_context)stream->priv, 0); + if (ret < 0) return ret; + xmp_play_buffer((xmp_context)stream->priv, NULL, 0, 0); /* reset internal state */ + return 0; +} + +snd_codec_t xmp_codec = +{ + CODECTYPE_MOD, + true, /* always available. */ + "s3m", + S_XMP_CodecInitialize, + S_XMP_CodecShutdown, + S_XMP_CodecOpenStream, + S_XMP_CodecReadStream, + S_XMP_CodecRewindStream, + S_XMP_CodecJumpToOrder, + S_XMP_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_XMP */ diff --git a/Quake/snd_xmp.hpp b/Quake/snd_xmp.hpp new file mode 100644 index 0000000..9516a49 --- /dev/null +++ b/Quake/snd_xmp.hpp @@ -0,0 +1,12 @@ +/* module tracker decoding support using libxmp */ +#if !defined(_SND_XMP_H_) +#define _SND_XMP_H_ + +#if defined(USE_CODEC_XMP) + +extern snd_codec_t xmp_codec; + +#endif /* USE_CODEC_XMP */ + +#endif /* ! _SND_XMP_H_ */ + diff --git a/Quake/spritegn.hpp b/Quake/spritegn.hpp new file mode 100644 index 0000000..8174842 --- /dev/null +++ b/Quake/spritegn.hpp @@ -0,0 +1,117 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __SPRITEGEN_H +#define __SPRITEGEN_H + +// +// spritegn.h: header file for sprite generation program +// + +// ********************************************************** +// * This file must be identical in the spritegen directory * +// * and in the Quake directory, because it's used to * +// * pass data from one to the other via .spr files. * +// ********************************************************** + +//------------------------------------------------------- +// This program generates .spr sprite package files. +// The format of the files is as follows: +// +// dsprite_t file header structure +// +// +// dspriteframe_t frame header structure +// sprite bitmap +// +// dspriteframe_t frame header structure +// sprite bitmap +// +//------------------------------------------------------- + +#ifdef INCLUDELIBS + +#include +#include +#include +#include + +#include "cmdlib.h" +#include "scriplib.h" +#include "dictlib.h" +#include "trilib.h" +#include "lbmlib.h" +#include "mathlib.h" + +#endif + +#define SPRITE_VERSION 1 + +// must match definition in modelgen.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum {ST_SYNC=0, ST_RAND } synctype_t; +#endif + +// TODO: shorten these? +typedef struct { + int ident; + int version; + int type; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dsprite_t; + +#define SPR_VP_PARALLEL_UPRIGHT 0 +#define SPR_FACING_UPRIGHT 1 +#define SPR_VP_PARALLEL 2 +#define SPR_ORIENTED 3 +#define SPR_VP_PARALLEL_ORIENTED 4 + +typedef struct { + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct { + int numframes; +} dspritegroup_t; + +typedef struct { + float interval; +} dspriteinterval_t; + +typedef enum { SPR_SINGLE=0, SPR_GROUP, SPR_ANGLED } spriteframetype_t; + +typedef struct { + spriteframetype_t type; +} dspriteframetype_t; + +#define IDSPRITEHEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDSP" + +#endif /* __SPRITEGEN_H */ + diff --git a/Quake/stb_image_write.hpp b/Quake/stb_image_write.hpp new file mode 100644 index 0000000..260b0b4 --- /dev/null +++ b/Quake/stb_image_write.hpp @@ -0,0 +1,684 @@ +/* QuakeSpasm: kept only the jpg writer, the only thing we use, and removed all others. */ + +/* stb_image_write - v1.07 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicity, not optimal image file size + or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + + There are four functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const float *data); + + There are also four equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + TGA RLE + Alan Hickman + initial file IO callback implementation + Emmanuel Julien + JPEG + Jon Olick (original jo_jpeg.cpp code) + Daniel Gibson + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#define STBIWDEF extern +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +#if 0 /* not used in QuakeSpasm */ +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); +#endif + +#ifdef __cplusplus +} +#endif + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; i<=end0pos && DU[i]==0; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + unsigned char head1[] = { 0xFF,0xC0,0,0x11,8, /*[5]*/0,/*[6]*/0,/*[7]*/0,/*[8]*/0, + 3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + head1[5] = (unsigned char)(height>>8); + head1[6] = STBIW_UCHAR(height); + head1[7] = (unsigned char)(width>>8); + head1[8] = STBIW_UCHAR(width); + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + const unsigned char *imageData = (const unsigned char *)data; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + int x, y, pos; + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float YDU[64], UDU[64], VDU[64]; + for(row = y, pos = 0; row < y+8; ++row) { + for(col = x; col < x+8; ++col, ++pos) { + int p = row*width*comp + col*comp; + float r, g, b; + if(row >= height) { + p -= width*comp*(row+1 - height); + } + if(col >= width) { + p -= comp*(col+1 - width); + } + + r = imageData[p+0]; + g = imageData[p+ofsG]; + b = imageData[p+ofsB]; + YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; + UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; + VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +#if 0 /* not used in QuakeSpasm */ +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} +#endif + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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/Quake/strl_fn.hpp b/Quake/strl_fn.hpp new file mode 100644 index 0000000..3be2a32 --- /dev/null +++ b/Quake/strl_fn.hpp @@ -0,0 +1,16 @@ +/* strl_fn.h - header file for BSD strlcat and strlcpy */ + +#ifndef __STRLFUNCS_H +#define __STRLFUNCS_H + +/* use our own copies of strlcpy and strlcat taken from OpenBSD */ +#ifdef __cplusplus +extern "C" { +#endif +extern size_t q_strlcpy (char *dst, const char *src, size_t size); +extern size_t q_strlcat (char *dst, const char *src, size_t size); +#ifdef __cplusplus +} +#endif + +#endif /* __STRLFUNCS_H */ diff --git a/Quake/strlcat.cpp b/Quake/strlcat.cpp new file mode 100644 index 0000000..ec4a9d8 --- /dev/null +++ b/Quake/strlcat.cpp @@ -0,0 +1,59 @@ +/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ + +/* + * Copyright (c) 1998 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. + */ + +#include +#include + +#include "strl_fn.hpp" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ + +size_t +q_strlcat (char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} + diff --git a/Quake/strlcpy.cpp b/Quake/strlcpy.cpp new file mode 100644 index 0000000..2363025 --- /dev/null +++ b/Quake/strlcpy.cpp @@ -0,0 +1,54 @@ +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 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. + */ + +#include + +#include "strl_fn.hpp" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ + +size_t +q_strlcpy (char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + diff --git a/Quake/sv_main.cpp b/Quake/sv_main.cpp new file mode 100644 index 0000000..91c2e9e --- /dev/null +++ b/Quake/sv_main.cpp @@ -0,0 +1,1620 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_main.c -- server main program + +#include "quakedef.hpp" + +server_t sv; +server_static_t svs; + +static char localmodels[MAX_MODELS][8]; // inline model names for precache + +int sv_protocol = PROTOCOL_FITZQUAKE; //johnfitz + +extern qboolean pr_alpha_supported; //johnfitz +extern int pr_effects_mask; + +//============================================================================ + +/* +=============== +SV_Protocol_f +=============== +*/ +void SV_Protocol_f (void) +{ + int i; + + switch (Cmd_Argc()) + { + case 1: + Con_Printf ("\"sv_protocol\" is \"%i\"\n", sv_protocol); + break; + case 2: + i = atoi(Cmd_Argv(1)); + if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ) + Con_Printf ("sv_protocol must be %i or %i or %i\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ); + else + { + sv_protocol = i; + if (sv.active) + Con_Printf ("changes will not take effect until the next level load.\n"); + } + break; + default: + Con_SafePrintf ("usage: sv_protocol \n"); + break; + } +} + +/* +=============== +SV_Init +=============== +*/ +void SV_Init (void) +{ + int i; + const char *p; + extern cvar_t sv_maxvelocity; + extern cvar_t sv_gravity; + extern cvar_t sv_nostep; + extern cvar_t sv_freezenonclients; + extern cvar_t sv_friction; + extern cvar_t sv_edgefriction; + extern cvar_t sv_stopspeed; + extern cvar_t sv_maxspeed; + extern cvar_t sv_accelerate; + extern cvar_t sv_idealpitchscale; + extern cvar_t sv_aim; + extern cvar_t sv_altnoclip; //johnfitz + + sv.edicts = NULL; // ericw -- sv.edicts switched to use malloc() + + Cvar_RegisterVariable (&sv_maxvelocity); + Cvar_RegisterVariable (&sv_gravity); + Cvar_RegisterVariable (&sv_friction); + Cvar_SetCallback (&sv_gravity, Host_Callback_Notify); + Cvar_SetCallback (&sv_friction, Host_Callback_Notify); + Cvar_RegisterVariable (&sv_edgefriction); + Cvar_RegisterVariable (&sv_stopspeed); + Cvar_RegisterVariable (&sv_maxspeed); + Cvar_SetCallback (&sv_maxspeed, Host_Callback_Notify); + Cvar_RegisterVariable (&sv_accelerate); + Cvar_RegisterVariable (&sv_idealpitchscale); + Cvar_RegisterVariable (&sv_aim); + Cvar_RegisterVariable (&sv_nostep); + Cvar_RegisterVariable (&sv_freezenonclients); + Cvar_RegisterVariable (&sv_altnoclip); //johnfitz + + Cmd_AddCommand ("sv_protocol", &SV_Protocol_f); //johnfitz + + for (i=0 ; i MAX_DATAGRAM-18) + return; + MSG_WriteByte (&sv.datagram, svc_particle); + MSG_WriteCoord (&sv.datagram, org[0], sv.protocolflags); + MSG_WriteCoord (&sv.datagram, org[1], sv.protocolflags); + MSG_WriteCoord (&sv.datagram, org[2], sv.protocolflags); + for (i=0 ; i<3 ; i++) + { + v = dir[i]*16; + if (v > 127) + v = 127; + else if (v < -128) + v = -128; + MSG_WriteChar (&sv.datagram, v); + } + MSG_WriteByte (&sv.datagram, count); + MSG_WriteByte (&sv.datagram, color); +} + +/* +================== +SV_StartSound + +Each entity can have eight independant sound sources, like voice, +weapon, feet, etc. + +Channel 0 is an auto-allocate channel, the others override anything +already running on that entity/channel pair. + +An attenuation of 0 will play full volume everywhere in the level. +Larger attenuations will drop off. (max 4 attenuation) + +================== +*/ +void SV_StartSound (edict_t *entity, int channel, const char *sample, int volume, float attenuation) +{ + int sound_num, ent; + int i, field_mask; + + if (volume < 0 || volume > 255) + Host_Error ("SV_StartSound: volume = %i", volume); + + if (attenuation < 0 || attenuation > 4) + Host_Error ("SV_StartSound: attenuation = %f", attenuation); + + if (channel < 0 || channel > 7) + Host_Error ("SV_StartSound: channel = %i", channel); + + if (sv.datagram.cursize > MAX_DATAGRAM-21) + return; + +// find precache number for sound + for (sound_num = 1; sound_num < MAX_SOUNDS && sv.sound_precache[sound_num]; sound_num++) + { + if (!strcmp(sample, sv.sound_precache[sound_num])) + break; + } + + if (sound_num == MAX_SOUNDS || !sv.sound_precache[sound_num]) + { + Con_Printf ("SV_StartSound: %s not precached\n", sample); + return; + } + + ent = NUM_FOR_EDICT(entity); + + field_mask = 0; + if (volume != DEFAULT_SOUND_PACKET_VOLUME) + field_mask |= SND_VOLUME; + if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) + field_mask |= SND_ATTENUATION; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (ent >= 8192) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; //don't send any info protocol can't support + field_mask |= SND_LARGEENTITY; + } + if (sound_num >= 256 || channel >= 8) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; //don't send any info protocol can't support + field_mask |= SND_LARGESOUND; + } + //johnfitz + + if (sv.datagram.cursize > MAX_DATAGRAM-21) + return; + +// directed messages go only to the entity the are targeted on + MSG_WriteByte (&sv.datagram, svc_sound); + MSG_WriteByte (&sv.datagram, field_mask); + if (field_mask & SND_VOLUME) + MSG_WriteByte (&sv.datagram, volume); + if (field_mask & SND_ATTENUATION) + MSG_WriteByte (&sv.datagram, attenuation*64); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (field_mask & SND_LARGEENTITY) + { + MSG_WriteShort (&sv.datagram, ent); + MSG_WriteByte (&sv.datagram, channel); + } + else + MSG_WriteShort (&sv.datagram, (ent<<3) | channel); + if (field_mask & SND_LARGESOUND) + MSG_WriteShort (&sv.datagram, sound_num); + else + MSG_WriteByte (&sv.datagram, sound_num); + //johnfitz + + for (i = 0; i < 3; i++) + MSG_WriteCoord (&sv.datagram, entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]), sv.protocolflags); +} + +/* +================== +SV_LocalSound - for 2021 rerelease +================== +*/ +void SV_LocalSound (client_t *client, const char *sample) +{ + int sound_num, field_mask; + + for (sound_num = 1; sound_num < MAX_SOUNDS && sv.sound_precache[sound_num]; sound_num++) + { + if (!strcmp(sample, sv.sound_precache[sound_num])) + break; + } + if (sound_num == MAX_SOUNDS || !sv.sound_precache[sound_num]) + { + Con_Printf ("SV_LocalSound: %s not precached\n", sample); + return; + } + + field_mask = 0; + if (sound_num >= 256) + { + if (sv.protocol == PROTOCOL_NETQUAKE) + return; + field_mask = SND_LARGESOUND; + } + + if (client->message.cursize > client->message.maxsize-4) + return; + + MSG_WriteByte (&client->message, svc_localsound); + MSG_WriteByte (&client->message, field_mask); + if (field_mask & SND_LARGESOUND) + MSG_WriteShort (&client->message, sound_num); + else + MSG_WriteByte (&client->message, sound_num); +} + +/* +============================================================================== + +CLIENT SPAWNING + +============================================================================== +*/ + +static qboolean SV_IsLocalClient (client_t *client) +{ + return Q_strcmp (NET_QSocketGetAddressString (client->netconnection), "LOCAL") == 0; +} + +/* +================ +SV_SendServerinfo + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each server load. +================ +*/ +void SV_SendServerinfo (client_t *client) +{ + const char **s; + char message[2048]; + int i; //johnfitz + + MSG_WriteByte (&client->message, svc_print); + sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version + MSG_WriteString (&client->message,message); + + MSG_WriteByte (&client->message, svc_serverinfo); + MSG_WriteLong (&client->message, sv.protocol); //johnfitz -- sv.protocol instead of PROTOCOL_VERSION + + if (sv.protocol == PROTOCOL_RMQ) + { + // mh - now send protocol flags so that the client knows the protocol features to expect + MSG_WriteLong (&client->message, sv.protocolflags); + } + + MSG_WriteByte (&client->message, svs.maxclients); + + if (!coop.value && deathmatch.value) + MSG_WriteByte (&client->message, GAME_DEATHMATCH); + else + MSG_WriteByte (&client->message, GAME_COOP); + + MSG_WriteString (&client->message, PR_GetString(sv.edicts->v.message)); + + //johnfitz -- only send the first 256 model and sound precaches if protocol is 15 + for (i = 1, s = sv.model_precache+1; *s; s++,i++) + if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) + MSG_WriteString (&client->message, *s); + MSG_WriteByte (&client->message, 0); + + for (i = 1, s = sv.sound_precache+1; *s; s++, i++) + if (sv.protocol != PROTOCOL_NETQUAKE || i < 256) + MSG_WriteString (&client->message, *s); + MSG_WriteByte (&client->message, 0); + //johnfitz + +// send music + MSG_WriteByte (&client->message, svc_cdtrack); + MSG_WriteByte (&client->message, sv.edicts->v.sounds); + MSG_WriteByte (&client->message, sv.edicts->v.sounds); + +// set view + MSG_WriteByte (&client->message, svc_setview); + MSG_WriteShort (&client->message, NUM_FOR_EDICT(client->edict)); + + MSG_WriteByte (&client->message, svc_signonnum); + MSG_WriteByte (&client->message, 1); + + client->sendsignon = PRESPAWN_FLUSH; + client->spawned = false; // need prespawn, spawn, etc +} + +/* +================ +SV_ConnectClient + +Initializes a client_t for a new net connection. This will only be called +once for a player each game, not once for each level change. +================ +*/ +void SV_ConnectClient (int clientnum) +{ + edict_t *ent; + client_t *client; + int edictnum; + struct qsocket_s *netconnection; + int i; + float spawn_parms[NUM_SPAWN_PARMS]; + + client = svs.clients + clientnum; + + Con_DPrintf ("Client %s connected\n", NET_QSocketGetAddressString(client->netconnection)); + + edictnum = clientnum+1; + + ent = EDICT_NUM(edictnum); + +// set up the client_t + netconnection = client->netconnection; + + if (sv.loadgame) + memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms)); + memset (client, 0, sizeof(*client)); + client->netconnection = netconnection; + + strcpy (client->name, "unconnected"); + client->active = true; + client->spawned = false; + client->edict = ent; + client->message.data = client->msgbuf; + client->message.maxsize = sizeof(client->msgbuf); + client->message.allowoverflow = true; // we can catch it + + if (sv.loadgame) + memcpy (client->spawn_parms, spawn_parms, sizeof(spawn_parms)); + else + { + // call the progs to get default spawn parms for the new client + PR_ExecuteProgram (pr_global_struct->SetNewParms); + for (i=0 ; ispawn_parms[i] = (&pr_global_struct->parm1)[i]; + } + + SV_SendServerinfo (client); +} + + +/* +=================== +SV_CheckForNewClients + +=================== +*/ +void SV_CheckForNewClients (void) +{ + struct qsocket_s *ret; + int i; + +// +// check for new connections +// + while (1) + { + ret = NET_CheckNewConnections (); + if (!ret) + break; + + // + // init a new client structure + // + for (i=0 ; icontents < 0) + { + if (node->contents != CONTENTS_SOLID) + { + pvs = Mod_LeafPVS ( (mleaf_t *)node, worldmodel); //johnfitz -- worldmodel as a parameter + for (i=0 ; iplane; + d = DotProduct (org, plane->normal) - plane->dist; + if (d > 8) + node = node->children[0]; + else if (d < -8) + node = node->children[1]; + else + { // go down both + SV_AddToFatPVS (org, node->children[0], worldmodel); //johnfitz -- worldmodel as a parameter + node = node->children[1]; + } + } +} + +/* +============= +SV_FatPVS + +Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the +given point. +============= +*/ +byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter +{ + fatbytes = (worldmodel->numleafs+7)>>3; // ericw -- was +31, assumed to be a bug/typo + if (fatpvs == NULL || fatbytes > fatpvs_capacity) + { + fatpvs_capacity = fatbytes; + fatpvs = (byte *) realloc (fatpvs, fatpvs_capacity); + if (!fatpvs) + Sys_Error ("SV_FatPVS: realloc() failed on %d bytes", fatpvs_capacity); + } + + Q_memset (fatpvs, 0, fatbytes); + SV_AddToFatPVS (org, worldmodel->nodes, worldmodel); //johnfitz -- worldmodel as a parameter + return fatpvs; +} + +/* +============= +SV_VisibleToClient -- johnfitz + +PVS test encapsulated in a nice function +============= +*/ +qboolean SV_VisibleToClient (edict_t *client, edict_t *test, qmodel_t *worldmodel) +{ + byte *pvs; + vec3_t org; + int i; + + VectorAdd (client->v.origin, client->v.view_ofs, org); + pvs = SV_FatPVS (org, worldmodel); + + for (i=0 ; i < test->num_leafs ; i++) + if (pvs[test->leafnums[i] >> 3] & (1 << (test->leafnums[i]&7) )) + return true; + + return false; +} + +//============================================================================= + +/* +============= +SV_WriteEntitiesToClient + +============= +*/ +void SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg) +{ + int e, i; + int bits; + byte *pvs; + vec3_t org; + float miss; + edict_t *ent; + eval_t *val; + +// find the client's PVS + VectorAdd (clent->v.origin, clent->v.view_ofs, org); + pvs = SV_FatPVS (org, sv.worldmodel); + +// send over all entities (excpet the client) that touch the pvs + ent = NEXT_EDICT(sv.edicts); + for (e=1 ; ev.modelindex || !PR_GetString(ent->v.model)[0]) + continue; + + //johnfitz -- don't send model>255 entities if protocol is 15 + if (sv.protocol == PROTOCOL_NETQUAKE && (int)ent->v.modelindex & 0xFF00) + continue; + + // ignore if not touching a PV leaf + for (i=0 ; i < ent->num_leafs ; i++) + if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) )) + break; + + // ericw -- added ent->num_leafs < MAX_ENT_LEAFS condition. + // + // if ent->num_leafs == MAX_ENT_LEAFS, the ent is visible from too many leafs + // for us to say whether it's in the PVS, so don't try to vis cull it. + // this commonly happens with rotators, because they often have huge bboxes + // spanning the entire map, or really tall lifts, etc. + if (i == ent->num_leafs && ent->num_leafs < MAX_ENT_LEAFS) + continue; // not visible + } + + // johnfitz -- max size for protocol 15 is 18 bytes, not 16 as originally + // assumed here. And, for protocol 85 the max size is actually 24 bytes. + // For float coords and angles the limit is 40. + // FIXME: Use tighter limit according to protocol flags and send bits. + if (msg->cursize + 40 > msg->maxsize) + { + //johnfitz -- less spammy overflow message + if (!dev_overflows.packetsize || dev_overflows.packetsize + CONSOLE_RESPAM_TIME < realtime ) + { + Con_Printf ("Packet overflow!\n"); + dev_overflows.packetsize = realtime; + } + goto stats; + //johnfitz + } + +// send an update + bits = 0; + + for (i=0 ; i<3 ; i++) + { + miss = ent->v.origin[i] - ent->baseline.origin[i]; + if ( miss < -0.1 || miss > 0.1 ) + bits |= U_ORIGIN1<v.angles[0] != ent->baseline.angles[0] ) + bits |= U_ANGLE1; + + if ( ent->v.angles[1] != ent->baseline.angles[1] ) + bits |= U_ANGLE2; + + if ( ent->v.angles[2] != ent->baseline.angles[2] ) + bits |= U_ANGLE3; + + if (ent->v.movetype == MOVETYPE_STEP) + bits |= U_STEP; // don't mess up the step animation + + if (ent->baseline.colormap != ent->v.colormap) + bits |= U_COLORMAP; + + if (ent->baseline.skin != ent->v.skin) + bits |= U_SKIN; + + if (ent->baseline.frame != ent->v.frame) + bits |= U_FRAME; + + if ((ent->baseline.effects ^ (int)ent->v.effects) & pr_effects_mask) + bits |= U_EFFECTS; + + if (ent->baseline.modelindex != ent->v.modelindex) + bits |= U_MODEL; + + //johnfitz -- alpha + if (pr_alpha_supported) + { + // TODO: find a cleaner place to put this code + val = GetEdictFieldValue(ent, "alpha"); + if (val) + ent->alpha = ENTALPHA_ENCODE(val->_float); + } + + //don't send invisible entities unless they have effects + if (ent->alpha == ENTALPHA_ZERO && !((int)ent->v.effects & pr_effects_mask)) + continue; + //johnfitz + + val = GetEdictFieldValue(ent, "scale"); + if (val) + ent->scale = ENTSCALE_ENCODE(val->_float); + else + ent->scale = ENTSCALE_DEFAULT; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (sv.protocol != PROTOCOL_NETQUAKE) + { + + if (ent->baseline.alpha != ent->alpha) bits |= U_ALPHA; + if (ent->baseline.scale != ent->scale) bits |= U_SCALE; + if (bits & U_FRAME && (int)ent->v.frame & 0xFF00) bits |= U_FRAME2; + if (bits & U_MODEL && (int)ent->v.modelindex & 0xFF00) bits |= U_MODEL2; + if (ent->sendinterval) bits |= U_LERPFINISH; + if (bits >= 65536) bits |= U_EXTEND1; + if (bits >= 16777216) bits |= U_EXTEND2; + } + //johnfitz + + if (e >= 256) + bits |= U_LONGENTITY; + + if (bits >= 256) + bits |= U_MOREBITS; + + // + // write the message + // + MSG_WriteByte (msg, bits | U_SIGNAL); + + if (bits & U_MOREBITS) + MSG_WriteByte (msg, bits>>8); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & U_EXTEND1) + MSG_WriteByte(msg, bits>>16); + if (bits & U_EXTEND2) + MSG_WriteByte(msg, bits>>24); + //johnfitz + + if (bits & U_LONGENTITY) + MSG_WriteShort (msg,e); + else + MSG_WriteByte (msg,e); + + if (bits & U_MODEL) + MSG_WriteByte (msg, ent->v.modelindex); + if (bits & U_FRAME) + MSG_WriteByte (msg, ent->v.frame); + if (bits & U_COLORMAP) + MSG_WriteByte (msg, ent->v.colormap); + if (bits & U_SKIN) + MSG_WriteByte (msg, ent->v.skin); + if (bits & U_EFFECTS) + MSG_WriteByte (msg, (int)ent->v.effects & pr_effects_mask); + if (bits & U_ORIGIN1) + MSG_WriteCoord (msg, ent->v.origin[0], sv.protocolflags); + if (bits & U_ANGLE1) + MSG_WriteAngle(msg, ent->v.angles[0], sv.protocolflags); + if (bits & U_ORIGIN2) + MSG_WriteCoord (msg, ent->v.origin[1], sv.protocolflags); + if (bits & U_ANGLE2) + MSG_WriteAngle(msg, ent->v.angles[1], sv.protocolflags); + if (bits & U_ORIGIN3) + MSG_WriteCoord (msg, ent->v.origin[2], sv.protocolflags); + if (bits & U_ANGLE3) + MSG_WriteAngle(msg, ent->v.angles[2], sv.protocolflags); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & U_ALPHA) + MSG_WriteByte(msg, ent->alpha); + if (bits & U_SCALE) + MSG_WriteByte(msg, ent->scale); + if (bits & U_FRAME2) + MSG_WriteByte(msg, (int)ent->v.frame >> 8); + if (bits & U_MODEL2) + MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); + if (bits & U_LERPFINISH) + MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-sv.time)*255))); + //johnfitz + } + + //johnfitz -- devstats +stats: + if (msg->cursize > 1024 && dev_peakstats.packetsize <= 1024) + Con_DWarning ("%i byte packet exceeds standard limit of 1024 (max = %d).\n", msg->cursize, msg->maxsize); + dev_stats.packetsize = msg->cursize; + dev_peakstats.packetsize = q_max(msg->cursize, dev_peakstats.packetsize); + //johnfitz +} + +/* +============= +SV_CleanupEnts + +============= +*/ +void SV_CleanupEnts (void) +{ + int e; + edict_t *ent; + + ent = NEXT_EDICT(sv.edicts); + for (e=1 ; ev.effects = (int)ent->v.effects & ~EF_MUZZLEFLASH; + } +} + +/* +================== +SV_WriteClientdataToMessage + +================== +*/ +void SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg) +{ + int bits; + int i; + edict_t *other; + int items; + eval_t *val; + +// +// send a damage message +// + if (ent->v.dmg_take || ent->v.dmg_save) + { + other = PROG_TO_EDICT(ent->v.dmg_inflictor); + MSG_WriteByte (msg, svc_damage); + MSG_WriteByte (msg, ent->v.dmg_save); + MSG_WriteByte (msg, ent->v.dmg_take); + for (i=0 ; i<3 ; i++) + MSG_WriteCoord (msg, other->v.origin[i] + 0.5*(other->v.mins[i] + other->v.maxs[i]), sv.protocolflags ); + + ent->v.dmg_take = 0; + ent->v.dmg_save = 0; + } + +// +// send the current viewpos offset from the view entity +// + SV_SetIdealPitch (); // how much to look up / down ideally + +// a fixangle might get lost in a dropped packet. Oh well. + if ( ent->v.fixangle ) + { + MSG_WriteByte (msg, svc_setangle); + for (i=0 ; i < 3 ; i++) + MSG_WriteAngle (msg, ent->v.angles[i], sv.protocolflags ); + ent->v.fixangle = 0; + } + + bits = 0; + + if (ent->v.view_ofs[2] != DEFAULT_VIEWHEIGHT) + bits |= SU_VIEWHEIGHT; + + if (ent->v.idealpitch) + bits |= SU_IDEALPITCH; + +// stuff the sigil bits into the high bits of items for sbar, or else +// mix in items2 + val = GetEdictFieldValue(ent, "items2"); + + if (val) + items = (int)ent->v.items | ((int)val->_float << 23); + else + items = (int)ent->v.items | ((int)pr_global_struct->serverflags << 28); + + bits |= SU_ITEMS; + + if ( (int)ent->v.flags & FL_ONGROUND) + bits |= SU_ONGROUND; + + if ( ent->v.waterlevel >= 2) + bits |= SU_INWATER; + + for (i=0 ; i<3 ; i++) + { + if (ent->v.punchangle[i]) + bits |= (SU_PUNCH1<v.velocity[i]) + bits |= (SU_VELOCITY1<v.weaponframe) + bits |= SU_WEAPONFRAME; + + if (ent->v.armorvalue) + bits |= SU_ARMOR; + +// if (ent->v.weapon) + bits |= SU_WEAPON; + + //johnfitz -- PROTOCOL_FITZQUAKE + if (sv.protocol != PROTOCOL_NETQUAKE) + { + if (bits & SU_WEAPON && SV_ModelIndex(PR_GetString(ent->v.weaponmodel)) & 0xFF00) bits |= SU_WEAPON2; + if ((int)ent->v.armorvalue & 0xFF00) bits |= SU_ARMOR2; + if ((int)ent->v.currentammo & 0xFF00) bits |= SU_AMMO2; + if ((int)ent->v.ammo_shells & 0xFF00) bits |= SU_SHELLS2; + if ((int)ent->v.ammo_nails & 0xFF00) bits |= SU_NAILS2; + if ((int)ent->v.ammo_rockets & 0xFF00) bits |= SU_ROCKETS2; + if ((int)ent->v.ammo_cells & 0xFF00) bits |= SU_CELLS2; + if (bits & SU_WEAPONFRAME && (int)ent->v.weaponframe & 0xFF00) bits |= SU_WEAPONFRAME2; + if (bits & SU_WEAPON && ent->alpha != ENTALPHA_DEFAULT) bits |= SU_WEAPONALPHA; //for now, weaponalpha = client entity alpha + if (bits >= 65536) bits |= SU_EXTEND1; + if (bits >= 16777216) bits |= SU_EXTEND2; + } + //johnfitz + +// send the data + + MSG_WriteByte (msg, svc_clientdata); + MSG_WriteShort (msg, bits); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits>>16); + if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits>>24); + //johnfitz + + if (bits & SU_VIEWHEIGHT) + MSG_WriteChar (msg, ent->v.view_ofs[2]); + + if (bits & SU_IDEALPITCH) + MSG_WriteChar (msg, ent->v.idealpitch); + + for (i=0 ; i<3 ; i++) + { + if (bits & (SU_PUNCH1<v.punchangle[i]); + if (bits & (SU_VELOCITY1<v.velocity[i]/16); + } + +// [always sent] if (bits & SU_ITEMS) + MSG_WriteLong (msg, items); + + if (bits & SU_WEAPONFRAME) + MSG_WriteByte (msg, ent->v.weaponframe); + if (bits & SU_ARMOR) + MSG_WriteByte (msg, ent->v.armorvalue); + if (bits & SU_WEAPON) + MSG_WriteByte (msg, SV_ModelIndex(PR_GetString(ent->v.weaponmodel))); + + MSG_WriteShort (msg, ent->v.health); + MSG_WriteByte (msg, ent->v.currentammo); + MSG_WriteByte (msg, ent->v.ammo_shells); + MSG_WriteByte (msg, ent->v.ammo_nails); + MSG_WriteByte (msg, ent->v.ammo_rockets); + MSG_WriteByte (msg, ent->v.ammo_cells); + + if (standard_quake) + { + MSG_WriteByte (msg, ent->v.weapon); + } + else + { + for(i=0;i<32;i++) + { + if ( ((int)ent->v.weapon) & (1<v.weaponmodel)) >> 8); + if (bits & SU_ARMOR2) + MSG_WriteByte (msg, (int)ent->v.armorvalue >> 8); + if (bits & SU_AMMO2) + MSG_WriteByte (msg, (int)ent->v.currentammo >> 8); + if (bits & SU_SHELLS2) + MSG_WriteByte (msg, (int)ent->v.ammo_shells >> 8); + if (bits & SU_NAILS2) + MSG_WriteByte (msg, (int)ent->v.ammo_nails >> 8); + if (bits & SU_ROCKETS2) + MSG_WriteByte (msg, (int)ent->v.ammo_rockets >> 8); + if (bits & SU_CELLS2) + MSG_WriteByte (msg, (int)ent->v.ammo_cells >> 8); + if (bits & SU_WEAPONFRAME2) + MSG_WriteByte (msg, (int)ent->v.weaponframe >> 8); + if (bits & SU_WEAPONALPHA) + MSG_WriteByte (msg, ent->alpha); //for now, weaponalpha = client entity alpha + //johnfitz +} + +/* +======================= +SV_SendClientDatagram +======================= +*/ +qboolean SV_SendClientDatagram (client_t *client) +{ + byte buf[MAX_DATAGRAM]; + sizebuf_t msg; + + msg.data = buf; + msg.maxsize = sizeof(buf); + msg.cursize = 0; + + //johnfitz -- if client is nonlocal, use smaller max size so packets aren't fragmented + if (Q_strcmp(NET_QSocketGetAddressString(client->netconnection), "LOCAL") != 0) + msg.maxsize = DATAGRAM_MTU; + //johnfitz + + MSG_WriteByte (&msg, svc_time); + MSG_WriteFloat (&msg, sv.time); + +// add the client specific data to the datagram + SV_WriteClientdataToMessage (client->edict, &msg); + + SV_WriteEntitiesToClient (client->edict, &msg); + +// copy the server datagram if there is space + if (msg.cursize + sv.datagram.cursize < msg.maxsize) + SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); + +// send the datagram + if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) + { + SV_DropClient (true);// if the message couldn't send, kick off + return false; + } + + return true; +} + +/* +======================= +SV_UpdateToReliableMessages +======================= +*/ +void SV_UpdateToReliableMessages (void) +{ + int i, j; + client_t *client; + +// check for changes to be sent over the reliable streams + for (i=0, host_client = svs.clients ; iold_frags != host_client->edict->v.frags) + { + for (j=0, client = svs.clients ; jactive) + continue; + MSG_WriteByte (&client->message, svc_updatefrags); + MSG_WriteByte (&client->message, i); + MSG_WriteShort (&client->message, host_client->edict->v.frags); + } + + host_client->old_frags = host_client->edict->v.frags; + } + } + + for (j=0, client = svs.clients ; jactive) + continue; + SZ_Write (&client->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); + } + + SZ_Clear (&sv.reliable_datagram); +} + + +/* +======================= +SV_SendNop + +Send a nop message without trashing or sending the accumulated client +message buffer +======================= +*/ +void SV_SendNop (client_t *client) +{ + sizebuf_t msg; + byte buf[4]; + + msg.data = buf; + msg.maxsize = sizeof(buf); + msg.cursize = 0; + + MSG_WriteChar (&msg, svc_nop); + + if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) + SV_DropClient (true); // if the message couldn't send, kick off + client->last_message = realtime; +} + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages (void) +{ + int i; + +// update frags, names, etc + SV_UpdateToReliableMessages (); + +// build individual updates + for (i=0, host_client = svs.clients ; iactive) + continue; + + if (host_client->spawned) + { + if (!SV_SendClientDatagram (host_client)) + continue; + } + else + { + // the player isn't totally in the game yet + // send small keepalive messages if too much time has passed + // send a full message when the next signon stage has been requested + // some other message data (name changes, etc) may accumulate + // between signon stages + if (!host_client->sendsignon) + { + if (realtime - host_client->last_message > 5) + SV_SendNop (host_client); + continue; // don't send out non-signon messages + } + if (host_client->sendsignon == PRESPAWN_SIGNONBUFS) + { + qboolean local = SV_IsLocalClient (host_client); + while (host_client->signonidx < sv.num_signon_buffers) + { + sizebuf_t *signon = sv.signon_buffers[host_client->signonidx]; + if (host_client->message.cursize + signon->cursize > host_client->message.maxsize) + break; + SZ_Write (&host_client->message, signon->data, signon->cursize); + host_client->signonidx++; + // only send multiple buffers at once when playing locally, + // otherwise we send one signon at a time to avoid overflowing + // the datagram buffer for clients using a lower limit (e.g. 32000 in QS) + if (!local) + break; + } + if (host_client->signonidx == sv.num_signon_buffers) + host_client->sendsignon = PRESPAWN_SIGNONMSG; + } + if (host_client->sendsignon == PRESPAWN_SIGNONMSG) + { + if (host_client->message.cursize + 2 < host_client->message.maxsize) + { + MSG_WriteByte (&host_client->message, svc_signonnum); + MSG_WriteByte (&host_client->message, 2); + host_client->sendsignon = PRESPAWN_FLUSH; + } + } + } + + // check for an overflowed message. Should only happen + // on a very fucked up connection that backs up a lot, then + // changes level + if (host_client->message.overflowed) + { + SV_DropClient (true); + host_client->message.overflowed = false; + continue; + } + + if (host_client->message.cursize || host_client->dropasap) + { + if (!NET_CanSendMessage (host_client->netconnection)) + { +// I_Printf ("can't write\n"); + continue; + } + + if (host_client->dropasap) + SV_DropClient (false); // went to another level + else + { + if (NET_SendMessage (host_client->netconnection + , &host_client->message) == -1) + SV_DropClient (true); // if the message couldn't send, kick off + SZ_Clear (&host_client->message); + host_client->last_message = realtime; + if (host_client->sendsignon == PRESPAWN_FLUSH) + host_client->sendsignon = PRESPAWN_DONE; + } + } + } + + +// clear muzzle flashes + SV_CleanupEnts (); +} + + +/* +============================================================================== + +SERVER SPAWNING + +============================================================================== +*/ + +#define SIGNON_SIZE 31500 // QS has a MAX_DATAGRAM of 32000, try to play nice + +/* +================ +SV_AddSignonBuffer +================ +*/ +static void SV_AddSignonBuffer (void) +{ + sizebuf_t *sb; + if (sv.num_signon_buffers >= MAX_SIGNON_BUFFERS) + Host_Error ("SV_AddSignonBuffer overflow\n"); + + sb = (sizebuf_t *) Hunk_AllocName (sizeof (sizebuf_t) + SIGNON_SIZE, "signon"); + sb->data = (byte *)(sb + 1); + sb->maxsize = SIGNON_SIZE; + sv.signon_buffers[sv.num_signon_buffers++] = sb; + sv.signon = sb; +} + +/* +================ +SV_ReserveSignonSpace +================ +*/ +void SV_ReserveSignonSpace (int numbytes) +{ + if (sv.signon->cursize + numbytes > sv.signon->maxsize) + SV_AddSignonBuffer (); +} + +/* +================ +SV_ModelIndex + +================ +*/ +int SV_ModelIndex (const char *name) +{ + int i; + + if (!name || !name[0]) + return 0; + + for (i=0 ; ifree) + continue; + if (entnum > svs.maxclients && !svent->v.modelindex) + continue; + + // + // create entity baseline + // + VectorCopy (svent->v.origin, svent->baseline.origin); + VectorCopy (svent->v.angles, svent->baseline.angles); + svent->baseline.frame = svent->v.frame; + svent->baseline.skin = svent->v.skin; + if (entnum > 0 && entnum <= svs.maxclients) + { + svent->baseline.colormap = entnum; + svent->baseline.modelindex = SV_ModelIndex("progs/player.mdl"); + svent->baseline.alpha = ENTALPHA_DEFAULT; //johnfitz -- alpha support + svent->baseline.scale = ENTSCALE_DEFAULT; + } + else + { + svent->baseline.colormap = 0; + svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model)); + svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support + svent->baseline.scale = ENTSCALE_DEFAULT; + if (sv.protocol == PROTOCOL_RMQ) + { + eval_t* val; + val = GetEdictFieldValue(svent, "scale"); + if (val) + svent->baseline.scale = ENTSCALE_ENCODE(val->_float); + } + } + + //johnfitz -- PROTOCOL_FITZQUAKE + bits = 0; + if (sv.protocol == PROTOCOL_NETQUAKE) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values + { + if (svent->baseline.modelindex & 0xFF00) + svent->baseline.modelindex = 0; + if (svent->baseline.frame & 0xFF00) + svent->baseline.frame = 0; + svent->baseline.alpha = ENTALPHA_DEFAULT; + svent->baseline.scale = ENTSCALE_DEFAULT; + } + else //decide which extra data needs to be sent + { + if (svent->baseline.modelindex & 0xFF00) + bits |= B_LARGEMODEL; + if (svent->baseline.frame & 0xFF00) + bits |= B_LARGEFRAME; + if (svent->baseline.alpha != ENTALPHA_DEFAULT) + bits |= B_ALPHA; + if (svent->baseline.scale != ENTSCALE_DEFAULT) + bits |= B_SCALE; + } + //johnfitz + + // + // add to the message + // + SV_ReserveSignonSpace (35); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits) + MSG_WriteByte (sv.signon, svc_spawnbaseline2); + else + MSG_WriteByte (sv.signon, svc_spawnbaseline); + //johnfitz + + MSG_WriteShort (sv.signon,entnum); + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits) + MSG_WriteByte (sv.signon, bits); + + if (bits & B_LARGEMODEL) + MSG_WriteShort (sv.signon, svent->baseline.modelindex); + else + MSG_WriteByte (sv.signon, svent->baseline.modelindex); + + if (bits & B_LARGEFRAME) + MSG_WriteShort (sv.signon, svent->baseline.frame); + else + MSG_WriteByte (sv.signon, svent->baseline.frame); + //johnfitz + + MSG_WriteByte (sv.signon, svent->baseline.colormap); + MSG_WriteByte (sv.signon, svent->baseline.skin); + for (i=0 ; i<3 ; i++) + { + MSG_WriteCoord(sv.signon, svent->baseline.origin[i], sv.protocolflags); + MSG_WriteAngle(sv.signon, svent->baseline.angles[i], sv.protocolflags); + } + + //johnfitz -- PROTOCOL_FITZQUAKE + if (bits & B_ALPHA) + MSG_WriteByte (sv.signon, svent->baseline.alpha); + //johnfitz + + if (bits & B_SCALE) + MSG_WriteByte (sv.signon, svent->baseline.scale); + } +} + + +/* +================ +SV_SendReconnect + +Tell all the clients that the server is changing levels +================ +*/ +void SV_SendReconnect (void) +{ + byte data[128]; + sizebuf_t msg; + + msg.data = data; + msg.cursize = 0; + msg.maxsize = sizeof(data); + + MSG_WriteChar (&msg, svc_stufftext); + MSG_WriteString (&msg, "reconnect\n"); + NET_SendToAll (&msg, 5.0); + + if (!isDedicated) + Cmd_ExecuteString ("reconnect\n", src_command); +} + + +/* +================ +SV_SaveSpawnparms + +Grabs the current state of each client for saving across the +transition to another level +================ +*/ +void SV_SaveSpawnparms (void) +{ + int i, j; + + svs.serverflags = pr_global_struct->serverflags; + + for (i=0, host_client = svs.clients ; iactive) + continue; + + // call the progs to get default spawn parms for the new client + pr_global_struct->self = EDICT_TO_PROG(host_client->edict); + PR_ExecuteProgram (pr_global_struct->SetChangeParms); + for (j=0 ; jspawn_parms[j] = (&pr_global_struct->parm1)[j]; + } +} + + +/* +================ +SV_SpawnServer + +This is called at the start of each level +================ +*/ +extern float scr_centertime_off; +void SV_SpawnServer (const char *server) +{ + static char dummy[8] = { 0,0,0,0,0,0,0,0 }; + edict_t *ent; + int i, signonsize; + + // let's not have any servers with no name + if (hostname.string[0] == 0) + Cvar_Set ("hostname", "UNNAMED"); + scr_centertime_off = 0; + + Con_DPrintf ("SpawnServer: %s\n",server); + svs.changelevel_issued = false; // now safe to issue another + +// +// tell all connected clients that we are going to a new level +// + if (sv.active) + { + SV_SendReconnect (); + } + +// +// make cvars consistant +// + if (coop.value) + Cvar_Set ("deathmatch", "0"); + current_skill = (int)(skill.value + 0.5); + if (current_skill < 0) + current_skill = 0; + if (current_skill > 3) + current_skill = 3; + + Cvar_SetValue ("skill", (float)current_skill); + +// +// set up the new server +// + //memset (&sv, 0, sizeof(sv)); + Host_ClearMemory (); + + q_strlcpy (sv.name, server, sizeof(sv.name)); + + sv.protocol = sv_protocol; // johnfitz + + if (sv.protocol == PROTOCOL_RMQ) + { + // set up the protocol flags used by this server + // (note - these could be cvar-ised so that server admins could choose the protocol features used by their servers) + sv.protocolflags = PRFL_INT32COORD | PRFL_SHORTANGLE; + } + else sv.protocolflags = 0; + +// load progs to get entity field count + PR_LoadProgs (); + +// allocate server memory + /* Host_ClearMemory() called above already cleared the whole sv structure */ + sv.max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); //johnfitz -- max_edicts cvar + sv.edicts = (edict_t *) malloc (sv.max_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() + + sv.datagram.maxsize = sizeof(sv.datagram_buf); + sv.datagram.cursize = 0; + sv.datagram.data = sv.datagram_buf; + + sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); + sv.reliable_datagram.cursize = 0; + sv.reliable_datagram.data = sv.reliable_datagram_buf; + + SV_AddSignonBuffer (); + +// leave slots at start for clients only + sv.num_edicts = svs.maxclients+1; + memset(sv.edicts, 0, sv.num_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() + for (i=0 ; inumsubmodels ; i++) + { + sv.model_precache[1+i] = localmodels[i]; + sv.models[i+1] = Mod_ForName (localmodels[i], false); + } + +// +// load the rest of the entities +// + ent = EDICT_NUM(0); + memset (&ent->v, 0, progs->entityfields * 4); + ent->free = false; + ent->v.model = PR_SetEngineString(sv.worldmodel->name); + ent->v.modelindex = 1; // world model + ent->v.solid = SOLID_BSP; + ent->v.movetype = MOVETYPE_PUSH; + + if (coop.value) + pr_global_struct->coop = coop.value; + else + pr_global_struct->deathmatch = deathmatch.value; + + pr_global_struct->mapname = PR_SetEngineString(sv.name); + +// serverflags are for cross level information (sigils) + pr_global_struct->serverflags = svs.serverflags; + + ED_LoadFromFile (sv.worldmodel->entities); + + sv.active = true; + +// all setup is completed, any further precache statements are errors + sv.state = ss_active; + +// run two frames to allow everything to settle + host_frametime = 0.1; + SV_Physics (); + SV_Physics (); + +// create a baseline for more efficient communications + SV_CreateBaseline (); + + //johnfitz -- warn if signon buffer larger than standard server can handle + for (i = 0, signonsize = 0; i < sv.num_signon_buffers; i++) + signonsize += sv.signon_buffers[i]->cursize; + if (signonsize > 64000-2) + Con_DWarning ("%i byte signon buffer exceeds QS limit of 63998.\n", signonsize); + else if (signonsize > 8000-2) //max size that will fit into 8000-sized client->message buffer with 2 extra bytes on the end + Con_DWarning ("%i byte signon buffer exceeds standard limit of 7998.\n", signonsize); + //johnfitz + +// send serverinfo to all connected clients + for (i=0,host_client = svs.clients ; iactive) + SV_SendServerinfo (host_client); + + Con_DPrintf ("Server spawned.\n"); +} + diff --git a/Quake/sv_move.cpp b/Quake/sv_move.cpp new file mode 100644 index 0000000..b642515 --- /dev/null +++ b/Quake/sv_move.cpp @@ -0,0 +1,419 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_move.c -- monster movement + +#include "quakedef.hpp" + +#define STEPSIZE 18 + +/* +============= +SV_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean SV_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->v.origin, ent->v.mins, mins); + VectorAdd (ent->v.origin, ent->v.maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (SV_PointContents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = SV_Move (start, vec3_origin, vec3_origin, stop, true, ent); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = SV_Move (start, vec3_origin, vec3_origin, stop, true, ent); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + edict_t *enemy; + +// try the move + VectorCopy (ent->v.origin, oldorg); + VectorAdd (ent->v.origin, move, neworg); + +// flying monsters don't step up + if ( (int)ent->v.flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->v.origin, move, neworg); + enemy = PROG_TO_EDICT(ent->v.enemy); + if (i == 0 && enemy != sv.edicts) + { + dz = ent->v.origin[2] - PROG_TO_EDICT(ent->v.enemy)->v.origin[2]; + if (dz > 40) + neworg[2] -= 8; + if (dz < 30) + neworg[2] += 8; + } + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, neworg, false, ent); + + if (trace.fraction == 1) + { + if ( ((int)ent->v.flags & FL_SWIM) && SV_PointContents(trace.endpos) == CONTENTS_EMPTY ) + return false; // swim monster left water + + VectorCopy (trace.endpos, ent->v.origin); + if (relink) + SV_LinkEdict (ent, true); + return true; + } + + if (enemy == sv.edicts) + break; + } + + return false; + } + +// push down from a step height above the wished position + neworg[2] += STEPSIZE; + VectorCopy (neworg, end); + end[2] -= STEPSIZE*2; + + trace = SV_Move (neworg, ent->v.mins, ent->v.maxs, end, false, ent); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= STEPSIZE; + trace = SV_Move (neworg, ent->v.mins, ent->v.maxs, end, false, ent); + if (trace.allsolid || trace.startsolid) + return false; + } + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( (int)ent->v.flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->v.origin, move, ent->v.origin); + if (relink) + SV_LinkEdict (ent, true); + ent->v.flags = (int)ent->v.flags & ~FL_ONGROUND; + // Con_Printf ("fall down\n"); + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->v.origin); + + if (!SV_CheckBottom (ent)) + { + if ( (int)ent->v.flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + SV_LinkEdict (ent, true); + return true; + } + VectorCopy (oldorg, ent->v.origin); + return false; + } + + if ( (int)ent->v.flags & FL_PARTIALGROUND ) + { + // Con_Printf ("back on ground\n"); + ent->v.flags = (int)ent->v.flags & ~FL_PARTIALGROUND; + } + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + +// the move is ok + if (relink) + SV_LinkEdict (ent, true); + return true; +} + + +//============================================================================ + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +void PF_changeyaw (void); +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + ent->v.ideal_yaw = yaw; + PF_changeyaw(); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->v.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + delta = ent->v.angles[YAW] - ent->v.ideal_yaw; + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->v.origin); + } + SV_LinkEdict (ent, true); + return true; + } + SV_LinkEdict (ent, true); + + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ +// Con_Printf ("SV_FixCheckBottom\n"); + ent->v.flags = (int)ent->v.flags | FL_PARTIALGROUND; +} + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + olddir = anglemod( (int)(actor->v.ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->v.origin[0] - actor->v.origin[0]; + deltay = enemy->v.origin[1] - actor->v.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs((int)deltay)>abs((int)deltax)) // ericw -- explicit int cast to suppress clang suggestion to use fabsf + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->v.ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!SV_CheckBottom (actor)) + SV_FixCheckBottom (actor); + +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->v.absmin[i] > ent->v.absmax[i] + dist) + return false; + if (goal->v.absmax[i] < ent->v.absmin[i] - dist) + return false; + } + return true; +} + +/* +====================== +SV_MoveToGoal + +====================== +*/ +void SV_MoveToGoal (void) +{ + edict_t *ent, *goal; + float dist; + + ent = PROG_TO_EDICT(pr_global_struct->self); + goal = PROG_TO_EDICT(ent->v.goalentity); + dist = G_FLOAT(OFS_PARM0); + + if ( !( (int)ent->v.flags & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) + { + G_FLOAT(OFS_RETURN) = 0; + return; + } + +// if the next step hits the enemy, return immediately + if ( PROG_TO_EDICT(ent->v.enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || + !SV_StepDirection (ent, ent->v.ideal_yaw, dist)) + { + SV_NewChaseDir (ent, goal, dist); + } +} + diff --git a/Quake/sv_phys.cpp b/Quake/sv_phys.cpp new file mode 100644 index 0000000..3575756 --- /dev/null +++ b/Quake/sv_phys.cpp @@ -0,0 +1,1233 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_phys.c + +#include "quakedef.hpp" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +cvar_t sv_friction = {"sv_friction","4",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t sv_stopspeed = {"sv_stopspeed","100",CVAR_NONE}; +cvar_t sv_gravity = {"sv_gravity","800",CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t sv_maxvelocity = {"sv_maxvelocity","2000",CVAR_NONE}; +cvar_t sv_nostep = {"sv_nostep","0",CVAR_NONE}; +cvar_t sv_freezenonclients = {"sv_freezenonclients","0",CVAR_NONE}; + + +#define MOVE_EPSILON 0.01 + +void SV_Physics_Toss (edict_t *ent); + +/* +================ +SV_CheckAllEnts +================ +*/ +void SV_CheckAllEnts (void) +{ + int e; + edict_t *check; + +// see if any solid entities are inside the final position + check = NEXT_EDICT(sv.edicts); + for (e=1 ; efree) + continue; + if (check->v.movetype == MOVETYPE_PUSH + || check->v.movetype == MOVETYPE_NONE + || check->v.movetype == MOVETYPE_NOCLIP) + continue; + + if (SV_TestEntityPosition (check)) + Con_Printf ("entity in invalid position\n"); + } +} + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (IS_NAN(ent->v.velocity[i])) + { + Con_Printf ("Got a NaN velocity on %s\n", PR_GetString(ent->v.classname)); + ent->v.velocity[i] = 0; + } + if (IS_NAN(ent->v.origin[i])) + { + Con_Printf ("Got a NaN origin on %s\n", PR_GetString(ent->v.classname)); + ent->v.origin[i] = 0; + } + if (ent->v.velocity[i] > sv_maxvelocity.value) + ent->v.velocity[i] = sv_maxvelocity.value; + else if (ent->v.velocity[i] < -sv_maxvelocity.value) + ent->v.velocity[i] = -sv_maxvelocity.value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code if time. There is some play in the exact time the think +function will be called, because it is called before any movement is done +in a frame. Not used for pushmove objects, because they must be exact. +Returns false if the entity removed itself. +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->v.nextthink; + if (thinktime <= 0 || thinktime > sv.time + host_frametime) + return true; + + if (thinktime < sv.time) + thinktime = sv.time; // don't let things stay in the past. + // it is possible to start that way + // by a trigger with a local time. + + ent->oldthinktime = thinktime; + ent->oldframe = ent->v.frame; //johnfitz + + ent->v.nextthink = 0; + pr_global_struct->time = thinktime; + pr_global_struct->self = EDICT_TO_PROG(ent); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + PR_ExecuteProgram (ent->v.think); + + return !ent->free; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, edict_t *e2) +{ + int old_self, old_other; + + old_self = pr_global_struct->self; + old_other = pr_global_struct->other; + + pr_global_struct->time = sv.time; + if (e1->v.touch && e1->v.solid != SOLID_NOT) + { + pr_global_struct->self = EDICT_TO_PROG(e1); + pr_global_struct->other = EDICT_TO_PROG(e2); + PR_ExecuteProgram (e1->v.touch); + } + + if (e2->v.touch && e2->v.solid != SOLID_NOT) + { + pr_global_struct->self = EDICT_TO_PROG(e2); + pr_global_struct->other = EDICT_TO_PROG(e1); + PR_ExecuteProgram (e2->v.touch); + } + + pr_global_struct->self = old_self; + pr_global_struct->other = old_other; +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +If steptrace is not NULL, the trace of any vertical wall hit will be stored +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, trace_t *steptrace) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->v.velocity, original_velocity); + VectorCopy (ent->v.velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + for (bumpcount=0 ; bumpcountv.velocity[0] && !ent->v.velocity[1] && !ent->v.velocity[2]) + break; + + for (i=0 ; i<3 ; i++) + end[i] = ent->v.origin[i] + time_left * ent->v.velocity[i]; + + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, false, ent); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->v.velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->v.origin); + VectorCopy (ent->v.velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + if (!trace.ent) + Sys_Error ("SV_FlyMove: !trace.ent"); + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if (trace.ent->v.solid == SOLID_BSP) + { + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + if (steptrace) + *steptrace = trace; // save for player extrafriction + } + +// +// run the impact function +// + SV_Impact (ent, trace.ent); + if (ent->free) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->v.velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; iv.velocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->v.velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->v.velocity); + VectorScale (dir, d, ent->v.velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->v.velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->v.velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + float ent_gravity; + eval_t *val; + + val = GetEdictFieldValue(ent, "gravity"); + if (val && val->_float) + ent_gravity = val->_float; + else + ent_gravity = 1.0; + + ent->v.velocity[2] -= ent_gravity * sv_gravity.value * host_frametime; +} + + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t end; + + VectorAdd (ent->v.origin, push, end); + + if (ent->v.movetype == MOVETYPE_FLYMISSILE) + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_MISSILE, ent); + else if (ent->v.solid == SOLID_TRIGGER || ent->v.solid == SOLID_NOT) + // only clip against bmodels + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NOMONSTERS, ent); + else + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent); + + VectorCopy (trace.endpos, ent->v.origin); + SV_LinkEdict (ent, true); + + if (trace.ent) + SV_Impact (ent, trace.ent); + + return trace; +} + + +/* +============ +SV_PushMove +============ +*/ +void SV_PushMove (edict_t *pusher, float movetime) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs, move; + vec3_t entorig, pushorig; + int num_moved; + edict_t **moved_edict; //johnfitz -- dynamically allocate + vec3_t *moved_from; //johnfitz -- dynamically allocate + int mark; //johnfitz + + if (!pusher->v.velocity[0] && !pusher->v.velocity[1] && !pusher->v.velocity[2]) + { + pusher->v.ltime += movetime; + return; + } + + for (i=0 ; i<3 ; i++) + { + move[i] = pusher->v.velocity[i] * movetime; + mins[i] = pusher->v.absmin[i] + move[i]; + maxs[i] = pusher->v.absmax[i] + move[i]; + } + + VectorCopy (pusher->v.origin, pushorig); + +// move the pusher to it's final position + + VectorAdd (pusher->v.origin, move, pusher->v.origin); + pusher->v.ltime += movetime; + SV_LinkEdict (pusher, false); + + //johnfitz -- dynamically allocate + mark = Hunk_LowMark (); + moved_edict = (edict_t **) Hunk_Alloc (sv.num_edicts*sizeof(edict_t *)); + moved_from = (vec3_t *) Hunk_Alloc (sv.num_edicts*sizeof(vec3_t)); + //johnfitz + +// see if any solid entities are inside the final position + num_moved = 0; + check = NEXT_EDICT(sv.edicts); + for (e=1 ; efree) + continue; + if (check->v.movetype == MOVETYPE_PUSH + || check->v.movetype == MOVETYPE_NONE + || check->v.movetype == MOVETYPE_NOCLIP) + continue; + + // if the entity is standing on the pusher, it will definately be moved + if ( ! ( ((int)check->v.flags & FL_ONGROUND) + && PROG_TO_EDICT(check->v.groundentity) == pusher) ) + { + if ( check->v.absmin[0] >= maxs[0] + || check->v.absmin[1] >= maxs[1] + || check->v.absmin[2] >= maxs[2] + || check->v.absmax[0] <= mins[0] + || check->v.absmax[1] <= mins[1] + || check->v.absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + // remove the onground flag for non-players + if (check->v.movetype != MOVETYPE_WALK) + check->v.flags = (int)check->v.flags & ~FL_ONGROUND; + + VectorCopy (check->v.origin, entorig); + VectorCopy (check->v.origin, moved_from[num_moved]); + moved_edict[num_moved] = check; + num_moved++; + + // try moving the contacted entity + pusher->v.solid = SOLID_NOT; + SV_PushEntity (check, move); + pusher->v.solid = SOLID_BSP; + + // if it is still inside the pusher, block + block = SV_TestEntityPosition (check); + if (block) + { // fail the move + if (check->v.mins[0] == check->v.maxs[0]) + continue; + if (check->v.solid == SOLID_NOT || check->v.solid == SOLID_TRIGGER) + { // corpse + check->v.mins[0] = check->v.mins[1] = 0; + VectorCopy (check->v.mins, check->v.maxs); + continue; + } + + VectorCopy (entorig, check->v.origin); + SV_LinkEdict (check, true); + + VectorCopy (pushorig, pusher->v.origin); + SV_LinkEdict (pusher, false); + pusher->v.ltime -= movetime; + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (pusher->v.blocked) + { + pr_global_struct->self = EDICT_TO_PROG(pusher); + pr_global_struct->other = EDICT_TO_PROG(check); + PR_ExecuteProgram (pusher->v.blocked); + } + + // move back any entities we already moved + for (i=0 ; iv.origin); + SV_LinkEdict (moved_edict[i], false); + } + Hunk_FreeToLowMark (mark); //johnfitz + return; + } + } + + Hunk_FreeToLowMark (mark); //johnfitz + +} + +/* +================ +SV_Physics_Pusher + +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + float thinktime; + float oldltime; + float movetime; + + oldltime = ent->v.ltime; + + thinktime = ent->v.nextthink; + if (thinktime < ent->v.ltime + host_frametime) + { + movetime = thinktime - ent->v.ltime; + if (movetime < 0) + movetime = 0; + } + else + movetime = host_frametime; + + if (movetime) + { + SV_PushMove (ent, movetime); // advances ent->v.ltime if not blocked + } + + if (thinktime > oldltime && thinktime <= ent->v.ltime) + { + ent->v.nextthink = 0; + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(ent); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + PR_ExecuteProgram (ent->v.think); + if (ent->free) + return; + } + +} + + +/* +=============================================================================== + +CLIENT MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_CheckStuck + +This is a big hack to try and fix the rare case of getting stuck in the world +clipping hull. +============= +*/ +void SV_CheckStuck (edict_t *ent) +{ + int i, j; + int z; + vec3_t org; + + if (!SV_TestEntityPosition(ent)) + { + VectorCopy (ent->v.origin, ent->v.oldorigin); + return; + } + + VectorCopy (ent->v.origin, org); + VectorCopy (ent->v.oldorigin, ent->v.origin); + if (!SV_TestEntityPosition(ent)) + { + Con_DPrintf ("Unstuck.\n"); + SV_LinkEdict (ent, true); + return; + } + + for (z=0 ; z< 18 ; z++) + for (i=-1 ; i <= 1 ; i++) + for (j=-1 ; j <= 1 ; j++) + { + ent->v.origin[0] = org[0] + i; + ent->v.origin[1] = org[1] + j; + ent->v.origin[2] = org[2] + z; + if (!SV_TestEntityPosition(ent)) + { + Con_DPrintf ("Unstuck.\n"); + SV_LinkEdict (ent, true); + return; + } + } + + VectorCopy (org, ent->v.origin); + Con_DPrintf ("player is stuck.\n"); +} + + +/* +============= +SV_CheckWater +============= +*/ +qboolean SV_CheckWater (edict_t *ent) +{ + vec3_t point; + int cont; + + point[0] = ent->v.origin[0]; + point[1] = ent->v.origin[1]; + point[2] = ent->v.origin[2] + ent->v.mins[2] + 1; + + ent->v.waterlevel = 0; + ent->v.watertype = CONTENTS_EMPTY; + cont = SV_PointContents (point); + if (cont <= CONTENTS_WATER) + { + ent->v.watertype = cont; + ent->v.waterlevel = 1; + point[2] = ent->v.origin[2] + (ent->v.mins[2] + ent->v.maxs[2])*0.5; + cont = SV_PointContents (point); + if (cont <= CONTENTS_WATER) + { + ent->v.waterlevel = 2; + point[2] = ent->v.origin[2] + ent->v.view_ofs[2]; + cont = SV_PointContents (point); + if (cont <= CONTENTS_WATER) + ent->v.waterlevel = 3; + } + } + + return ent->v.waterlevel > 1; +} + +/* +============ +SV_WallFriction + +============ +*/ +void SV_WallFriction (edict_t *ent, trace_t *trace) +{ + vec3_t forward, right, up; + float d, i; + vec3_t into, side; + + AngleVectors (ent->v.v_angle, forward, right, up); + d = DotProduct (trace->plane.normal, forward); + + d += 0.5; + if (d >= 0) + return; + +// cut the tangential velocity + i = DotProduct (trace->plane.normal, ent->v.velocity); + VectorScale (trace->plane.normal, i, into); + VectorSubtract (ent->v.velocity, into, side); + + ent->v.velocity[0] = side[0] * (1 + d); + ent->v.velocity[1] = side[1] * (1 + d); +} + +/* +===================== +SV_TryUnstick + +Player has come to a dead stop, possibly due to the problem with limited +float precision at some angle joins in the BSP hull. + +Try fixing by pushing one pixel in each direction. + +This is a hack, but in the interest of good gameplay... +====================== +*/ +int SV_TryUnstick (edict_t *ent, vec3_t oldvel) +{ + int i; + vec3_t oldorg; + vec3_t dir; + int clip; + trace_t steptrace; + + VectorCopy (ent->v.origin, oldorg); + VectorCopy (vec3_origin, dir); + + for (i=0 ; i<8 ; i++) + { +// try pushing a little in an axial direction + switch (i) + { + case 0: dir[0] = 2; dir[1] = 0; break; + case 1: dir[0] = 0; dir[1] = 2; break; + case 2: dir[0] = -2; dir[1] = 0; break; + case 3: dir[0] = 0; dir[1] = -2; break; + case 4: dir[0] = 2; dir[1] = 2; break; + case 5: dir[0] = -2; dir[1] = 2; break; + case 6: dir[0] = 2; dir[1] = -2; break; + case 7: dir[0] = -2; dir[1] = -2; break; + } + + SV_PushEntity (ent, dir); + +// retry the original move + ent->v.velocity[0] = oldvel[0]; + ent->v. velocity[1] = oldvel[1]; + ent->v. velocity[2] = 0; + clip = SV_FlyMove (ent, 0.1, &steptrace); + + if ( fabs(oldorg[1] - ent->v.origin[1]) > 4 + || fabs(oldorg[0] - ent->v.origin[0]) > 4 ) + { + // Con_DPrintf ("unstuck!\n"); + return clip; + } + +// go back to the original pos and try again + VectorCopy (oldorg, ent->v.origin); + } + + VectorCopy (vec3_origin, ent->v.velocity); + return 7; // still not moving +} + +/* +===================== +SV_WalkMove + +Only used by players +====================== +*/ +#define STEPSIZE 18 +void SV_WalkMove (edict_t *ent) +{ + vec3_t upmove, downmove; + vec3_t oldorg, oldvel; + vec3_t nosteporg, nostepvel; + int clip; + int oldonground; + trace_t steptrace, downtrace; + +// +// do a regular slide move unless it looks like you ran into a step +// + oldonground = (int)ent->v.flags & FL_ONGROUND; + ent->v.flags = (int)ent->v.flags & ~FL_ONGROUND; + + VectorCopy (ent->v.origin, oldorg); + VectorCopy (ent->v.velocity, oldvel); + + clip = SV_FlyMove (ent, host_frametime, &steptrace); + + if ( !(clip & 2) ) + return; // move didn't block on a step + + if (!oldonground && ent->v.waterlevel == 0) + return; // don't stair up while jumping + + if (ent->v.movetype != MOVETYPE_WALK) + return; // gibbed by a trigger + + if (sv_nostep.value) + return; + + if ( (int)sv_player->v.flags & FL_WATERJUMP ) + return; + + VectorCopy (ent->v.origin, nosteporg); + VectorCopy (ent->v.velocity, nostepvel); + +// +// try moving up and forward to go up a step +// + VectorCopy (oldorg, ent->v.origin); // back to start pos + + VectorCopy (vec3_origin, upmove); + VectorCopy (vec3_origin, downmove); + upmove[2] = STEPSIZE; + downmove[2] = -STEPSIZE + oldvel[2]*host_frametime; + +// move up + SV_PushEntity (ent, upmove); // FIXME: don't link? + +// move forward + ent->v.velocity[0] = oldvel[0]; + ent->v. velocity[1] = oldvel[1]; + ent->v. velocity[2] = 0; + clip = SV_FlyMove (ent, host_frametime, &steptrace); + +// check for stuckness, possibly due to the limited precision of floats +// in the clipping hulls + if (clip) + { + if ( fabs(oldorg[1] - ent->v.origin[1]) < 0.03125 + && fabs(oldorg[0] - ent->v.origin[0]) < 0.03125 ) + { // stepping up didn't make any progress + clip = SV_TryUnstick (ent, oldvel); + } + } + +// extra friction based on view angle + if ( clip & 2 ) + SV_WallFriction (ent, &steptrace); + +// move down + downtrace = SV_PushEntity (ent, downmove); // FIXME: don't link? + + if (downtrace.plane.normal[2] > 0.7) + { + if (ent->v.solid == SOLID_BSP) + { + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(downtrace.ent); + } + } + else + { +// if the push down didn't end up on good ground, use the move without +// the step up. This happens near wall / slope combinations, and can +// cause the player to hop up higher on a slope too steep to climb + VectorCopy (nosteporg, ent->v.origin); + VectorCopy (nostepvel, ent->v.velocity); + } +} + + +/* +================ +SV_Physics_Client + +Player character actions +================ +*/ +void SV_Physics_Client (edict_t *ent, int num) +{ + if ( ! svs.clients[num-1].active ) + return; // unconnected slot + +// +// call standard client pre-think +// + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(ent); + PR_ExecuteProgram (pr_global_struct->PlayerPreThink); + +// +// do a move +// + SV_CheckVelocity (ent); + +// +// decide which move function to call +// + switch ((int)ent->v.movetype) + { + case MOVETYPE_NONE: + if (!SV_RunThink (ent)) + return; + break; + + case MOVETYPE_WALK: + if (!SV_RunThink (ent)) + return; + if (!SV_CheckWater (ent) && ! ((int)ent->v.flags & FL_WATERJUMP) ) + SV_AddGravity (ent); + SV_CheckStuck (ent); + SV_WalkMove (ent); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_GIB: + SV_Physics_Toss (ent); + break; + + case MOVETYPE_FLY: + if (!SV_RunThink (ent)) + return; + SV_FlyMove (ent, host_frametime, NULL); + break; + + case MOVETYPE_NOCLIP: + if (!SV_RunThink (ent)) + return; + VectorMA (ent->v.origin, host_frametime, ent->v.velocity, ent->v.origin); + break; + + default: + Sys_Error ("SV_Physics_client: bad movetype %i", (int)ent->v.movetype); + } + +// +// call standard player post-think +// + SV_LinkEdict (ent, true); + + pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(ent); + PR_ExecuteProgram (pr_global_struct->PlayerPostThink); +} + +//============================================================================ + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->v.angles, host_frametime, ent->v.avelocity, ent->v.angles); + VectorMA (ent->v.origin, host_frametime, ent->v.velocity, ent->v.origin); + + SV_LinkEdict (ent, false); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_CheckWaterTransition + +============= +*/ +void SV_CheckWaterTransition (edict_t *ent) +{ + int cont; + + cont = SV_PointContents (ent->v.origin); + + if (!ent->v.watertype) + { // just spawned here + ent->v.watertype = cont; + ent->v.waterlevel = 1; + return; + } + + if (cont <= CONTENTS_WATER) + { + if (ent->v.watertype == CONTENTS_EMPTY) + { // just crossed into water + SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); + } + ent->v.watertype = cont; + ent->v.waterlevel = 1; + } + else + { + if (ent->v.watertype != CONTENTS_EMPTY) + { // just crossed into water + SV_StartSound (ent, 0, "misc/h2ohit1.wav", 255, 1); + } + ent->v.watertype = CONTENTS_EMPTY; + ent->v.waterlevel = cont; + } +} + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + + // regular thinking + if (!SV_RunThink (ent)) + return; + +// if onground, return without moving + if ( ((int)ent->v.flags & FL_ONGROUND) ) + return; + + SV_CheckVelocity (ent); + +// add gravity + if (ent->v.movetype != MOVETYPE_FLY + && ent->v.movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->v.angles, host_frametime, ent->v.avelocity, ent->v.angles); + +// move origin + VectorScale (ent->v.velocity, host_frametime, move); + trace = SV_PushEntity (ent, move); + if (trace.fraction == 1) + return; + if (ent->free) + return; + + if (ent->v.movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->v.velocity, trace.plane.normal, ent->v.velocity, backoff); + +// stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->v.velocity[2] < 60 || ent->v.movetype != MOVETYPE_BOUNCE) + { + ent->v.flags = (int)ent->v.flags | FL_ONGROUND; + ent->v.groundentity = EDICT_TO_PROG(trace.ent); + VectorCopy (vec3_origin, ent->v.velocity); + VectorCopy (vec3_origin, ent->v.avelocity); + } + } + +// check for in water + SV_CheckWaterTransition (ent); +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +============= +*/ +void SV_Physics_Step (edict_t *ent) +{ + qboolean hitsound; + +// freefall if not onground + if ( ! ((int)ent->v.flags & (FL_ONGROUND | FL_FLY | FL_SWIM) ) ) + { + if (ent->v.velocity[2] < sv_gravity.value*-0.1) + hitsound = true; + else + hitsound = false; + + SV_AddGravity (ent); + SV_CheckVelocity (ent); + SV_FlyMove (ent, host_frametime, NULL); + SV_LinkEdict (ent, true); + + if ( (int)ent->v.flags & FL_ONGROUND ) // just hit ground + { + if (hitsound) + SV_StartSound (ent, 0, "demon/dland2.wav", 255, 1); + } + } + +// regular thinking + SV_RunThink (ent); + + SV_CheckWaterTransition (ent); +} + + +//============================================================================ + +/* +================ +SV_Physics + +================ +*/ +void SV_Physics (void) +{ + int i; + int entity_cap; // For sv_freezenonclients + edict_t *ent; + +// let the progs know that a new frame has started + pr_global_struct->self = EDICT_TO_PROG(sv.edicts); + pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + pr_global_struct->time = sv.time; + PR_ExecuteProgram (pr_global_struct->StartFrame); + +//SV_CheckAllEnts (); + +// +// treat each object in turn +// + ent = sv.edicts; + + if (sv_freezenonclients.value) + entity_cap = svs.maxclients + 1; // Only run physics on clients and the world + else + entity_cap = sv.num_edicts; + + //for (i=0 ; ifree) + continue; + + if (pr_global_struct->force_retouch) + { + SV_LinkEdict (ent, true); // force retouch even for stationary + } + + if (i > 0 && i <= svs.maxclients) + SV_Physics_Client (ent, i); + else if (ent->v.movetype == MOVETYPE_PUSH) + SV_Physics_Pusher (ent); + else if (ent->v.movetype == MOVETYPE_NONE) + SV_Physics_None (ent); + else if (ent->v.movetype == MOVETYPE_NOCLIP) + SV_Physics_Noclip (ent); + else if (ent->v.movetype == MOVETYPE_STEP) + SV_Physics_Step (ent); + else if (ent->v.movetype == MOVETYPE_TOSS + || ent->v.movetype == MOVETYPE_GIB + || ent->v.movetype == MOVETYPE_BOUNCE + || ent->v.movetype == MOVETYPE_FLY + || ent->v.movetype == MOVETYPE_FLYMISSILE) + SV_Physics_Toss (ent); + else + Sys_Error ("SV_Physics: bad movetype %i", (int)ent->v.movetype); + + //johnfitz -- PROTOCOL_FITZQUAKE + //capture interval to nextthink here and send it to client for better + //lerp timing, but only if interval is not 0.1 (which client assumes) + ent->sendinterval = false; + if (!ent->free && ent->v.nextthink > sv.time && (ent->v.movetype == MOVETYPE_STEP || ent->v.movetype == MOVETYPE_WALK || ent->v.frame != ent->oldframe)) + { + int j = Q_rint((ent->v.nextthink-ent->oldthinktime)*255); + if (j >= 0 && j < 256 && j != 25 && j != 26) //25 and 26 are close enough to 0.1 to not send + ent->sendinterval = true; + } + //johnfitz + } + + if (pr_global_struct->force_retouch) + pr_global_struct->force_retouch--; + + if (!sv_freezenonclients.value) + sv.time += host_frametime; +} diff --git a/Quake/sv_user.cpp b/Quake/sv_user.cpp new file mode 100644 index 0000000..04f7822 --- /dev/null +++ b/Quake/sv_user.cpp @@ -0,0 +1,627 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// sv_user.c -- server code for moving users + +#include "quakedef.hpp" + +edict_t *sv_player; + +extern cvar_t sv_friction; +cvar_t sv_edgefriction = {"edgefriction", "2", CVAR_NONE}; +extern cvar_t sv_stopspeed; + +static vec3_t forward, right, up; + +// world +float *angles; +float *origin; +float *velocity; + +qboolean onground; + +usercmd_t cmd; + +cvar_t sv_idealpitchscale = {"sv_idealpitchscale","0.8",CVAR_NONE}; +cvar_t sv_altnoclip = {"sv_altnoclip","1",CVAR_ARCHIVE}; //johnfitz + +/* +=============== +SV_SetIdealPitch +=============== +*/ +#define MAX_FORWARD 6 +void SV_SetIdealPitch (void) +{ + float angleval, sinval, cosval; + trace_t tr; + vec3_t top, bottom; + float z[MAX_FORWARD]; + int i, j; + int step, dir, steps; + + if (!((int)sv_player->v.flags & FL_ONGROUND)) + return; + + angleval = sv_player->v.angles[YAW] * M_PI*2 / 360; + sinval = sin(angleval); + cosval = cos(angleval); + + for (i=0 ; iv.origin[0] + cosval*(i+3)*12; + top[1] = sv_player->v.origin[1] + sinval*(i+3)*12; + top[2] = sv_player->v.origin[2] + sv_player->v.view_ofs[2]; + + bottom[0] = top[0]; + bottom[1] = top[1]; + bottom[2] = top[2] - 160; + + tr = SV_Move (top, vec3_origin, vec3_origin, bottom, 1, sv_player); + if (tr.allsolid) + return; // looking at a wall, leave ideal the way is was + + if (tr.fraction == 1) + return; // near a dropoff + + z[i] = top[2] + tr.fraction*(bottom[2]-top[2]); + } + + dir = 0; + steps = 0; + for (j=1 ; j -ON_EPSILON && step < ON_EPSILON) + continue; + + if (dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ) ) + return; // mixed changes + + steps++; + dir = step; + } + + if (!dir) + { + sv_player->v.idealpitch = 0; + return; + } + + if (steps < 2) + return; + sv_player->v.idealpitch = -dir * sv_idealpitchscale.value; +} + + +/* +================== +SV_UserFriction + +================== +*/ +void SV_UserFriction (void) +{ + float *vel; + float speed, newspeed, control; + vec3_t start, stop; + float friction; + trace_t trace; + + vel = velocity; + + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (!speed) + return; + +// if the leading edge is over a dropoff, increase friction + start[0] = stop[0] = origin[0] + vel[0]/speed*16; + start[1] = stop[1] = origin[1] + vel[1]/speed*16; + start[2] = origin[2] + sv_player->v.mins[2]; + stop[2] = start[2] - 34; + + trace = SV_Move (start, vec3_origin, vec3_origin, stop, true, sv_player); + + if (trace.fraction == 1.0) + friction = sv_friction.value*sv_edgefriction.value; + else + friction = sv_friction.value; + +// apply friction + control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed; + newspeed = speed - host_frametime*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + +/* +============== +SV_Accelerate +============== +*/ +cvar_t sv_maxspeed = {"sv_maxspeed", "320", CVAR_NOTIFY|CVAR_SERVERINFO}; +cvar_t sv_accelerate = {"sv_accelerate", "10", CVAR_NONE}; +void SV_Accelerate (float wishspeed, const vec3_t wishdir) +{ + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + accelspeed = sv_accelerate.value*host_frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + velocity[i] += accelspeed*wishdir[i]; +} + +void SV_AirAccelerate (float wishspeed, vec3_t wishveloc) +{ + int i; + float addspeed, wishspd, accelspeed, currentspeed; + + wishspd = VectorNormalize (wishveloc); + if (wishspd > 30) + wishspd = 30; + currentspeed = DotProduct (velocity, wishveloc); + addspeed = wishspd - currentspeed; + if (addspeed <= 0) + return; +// accelspeed = sv_accelerate.value * host_frametime; + accelspeed = sv_accelerate.value*wishspeed * host_frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + velocity[i] += accelspeed*wishveloc[i]; +} + + +void DropPunchAngle (void) +{ + float len; + + len = VectorNormalize (sv_player->v.punchangle); + + len -= 10*host_frametime; + if (len < 0) + len = 0; + VectorScale (sv_player->v.punchangle, len, sv_player->v.punchangle); +} + +/* +=================== +SV_WaterMove + +=================== +*/ +void SV_WaterMove (void) +{ + int i; + vec3_t wishvel; + float speed, newspeed, wishspeed, addspeed, accelspeed; + +// +// user intentions +// + AngleVectors (sv_player->v.v_angle, forward, right, up); + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove; + + if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove) + wishvel[2] -= 60; // drift towards bottom + else + wishvel[2] += cmd.upmove; + + wishspeed = VectorLength(wishvel); + if (wishspeed > sv_maxspeed.value) + { + VectorScale (wishvel, sv_maxspeed.value/wishspeed, wishvel); + wishspeed = sv_maxspeed.value; + } + wishspeed *= 0.7; + +// +// water friction +// + speed = VectorLength (velocity); + if (speed) + { + newspeed = speed - host_frametime * speed * sv_friction.value; + if (newspeed < 0) + newspeed = 0; + VectorScale (velocity, newspeed/speed, velocity); + } + else + newspeed = 0; + +// +// water acceleration +// + if (!wishspeed) + return; + + addspeed = wishspeed - newspeed; + if (addspeed <= 0) + return; + + VectorNormalize (wishvel); + accelspeed = sv_accelerate.value * wishspeed * host_frametime; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i=0 ; i<3 ; i++) + velocity[i] += accelspeed * wishvel[i]; +} + +void SV_WaterJump (void) +{ + if (sv.time > sv_player->v.teleport_time + || !sv_player->v.waterlevel) + { + sv_player->v.flags = (int)sv_player->v.flags & ~FL_WATERJUMP; + sv_player->v.teleport_time = 0; + } + sv_player->v.velocity[0] = sv_player->v.movedir[0]; + sv_player->v.velocity[1] = sv_player->v.movedir[1]; +} + +/* +=================== +SV_NoclipMove -- johnfitz + +new, alternate noclip. old noclip is still handled in SV_AirMove +=================== +*/ +void SV_NoclipMove (void) +{ + AngleVectors (sv_player->v.v_angle, forward, right, up); + + velocity[0] = forward[0]*cmd.forwardmove + right[0]*cmd.sidemove; + velocity[1] = forward[1]*cmd.forwardmove + right[1]*cmd.sidemove; + velocity[2] = forward[2]*cmd.forwardmove + right[2]*cmd.sidemove; + velocity[2] += cmd.upmove*2; //doubled to match running speed + + if (VectorLength (velocity) > sv_maxspeed.value) + { + VectorNormalize (velocity); + VectorScale (velocity, sv_maxspeed.value, velocity); + } +} + +/* +=================== +SV_AirMove +=================== +*/ +void SV_AirMove (void) +{ + int i; + vec3_t wishvel, wishdir; + float wishspeed; + float fmove, smove; + + AngleVectors (sv_player->v.angles, forward, right, up); + + fmove = cmd.forwardmove; + smove = cmd.sidemove; + +// hack to not let you back into teleporter + if (sv.time < sv_player->v.teleport_time && fmove < 0) + fmove = 0; + + for (i=0 ; i<3 ; i++) + wishvel[i] = forward[i]*fmove + right[i]*smove; + + if ( (int)sv_player->v.movetype != MOVETYPE_WALK) + wishvel[2] = cmd.upmove; + else + wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + if (wishspeed > sv_maxspeed.value) + { + VectorScale (wishvel, sv_maxspeed.value/wishspeed, wishvel); + wishspeed = sv_maxspeed.value; + } + + if ( sv_player->v.movetype == MOVETYPE_NOCLIP) + { // noclip + VectorCopy (wishvel, velocity); + } + else if ( onground ) + { + SV_UserFriction (); + SV_Accelerate (wishspeed, wishdir); + } + else + { // not on ground, so little effect on velocity + SV_AirAccelerate (wishspeed, wishvel); + } +} + +/* +=================== +SV_ClientThink + +the move fields specify an intended velocity in pix/sec +the angle fields specify an exact angular motion in degrees +=================== +*/ +void SV_ClientThink (void) +{ + vec3_t v_angle; + + if (sv_player->v.movetype == MOVETYPE_NONE) + return; + + onground = (int)sv_player->v.flags & FL_ONGROUND; + + origin = sv_player->v.origin; + velocity = sv_player->v.velocity; + + DropPunchAngle (); + +// +// if dead, behave differently +// + if (sv_player->v.health <= 0) + return; + +// +// angles +// show 1/3 the pitch angle and all the roll angle + cmd = host_client->cmd; + angles = sv_player->v.angles; + + VectorAdd (sv_player->v.v_angle, sv_player->v.punchangle, v_angle); + angles[ROLL] = V_CalcRoll (sv_player->v.angles, sv_player->v.velocity)*4; + if (!sv_player->v.fixangle) + { + angles[PITCH] = -v_angle[PITCH]/3; + angles[YAW] = v_angle[YAW]; + } + + if ( (int)sv_player->v.flags & FL_WATERJUMP ) + { + SV_WaterJump (); + return; + } +// +// walk +// + //johnfitz -- alternate noclip + if (sv_player->v.movetype == MOVETYPE_NOCLIP && sv_altnoclip.value) + SV_NoclipMove (); + else if (sv_player->v.waterlevel >= 2 && sv_player->v.movetype != MOVETYPE_NOCLIP) + SV_WaterMove (); + else + SV_AirMove (); + //johnfitz +} + + +/* +=================== +SV_ReadClientMove +=================== +*/ +void SV_ReadClientMove (usercmd_t *move) +{ + int i; + vec3_t angle; + int bits; + +// read ping time + host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] + = sv.time - MSG_ReadFloat (); + host_client->num_pings++; + +// read current angles + for (i=0 ; i<3 ; i++) + //johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE + if (sv.protocol == PROTOCOL_NETQUAKE) + angle[i] = MSG_ReadAngle (sv.protocolflags); + else + angle[i] = MSG_ReadAngle16 (sv.protocolflags); + //johnfitz + + VectorCopy (angle, host_client->edict->v.v_angle); + +// read movement + move->forwardmove = MSG_ReadShort (); + move->sidemove = MSG_ReadShort (); + move->upmove = MSG_ReadShort (); + +// read buttons + bits = MSG_ReadByte (); + host_client->edict->v.button0 = bits & 1; + host_client->edict->v.button2 = (bits & 2)>>1; + + i = MSG_ReadByte (); + if (i) + host_client->edict->v.impulse = i; +} + +/* +=================== +SV_ReadClientMessage + +Returns false if the client should be killed +=================== +*/ +qboolean SV_ReadClientMessage (void) +{ + int ret; + int ccmd; + const char *s; + + do + { +nextmsg: + ret = NET_GetMessage (host_client->netconnection); + if (ret == -1) + { + Sys_Printf ("SV_ReadClientMessage: NET_GetMessage failed\n"); + return false; + } + if (!ret) + return true; + + MSG_BeginReading (); + + while (1) + { + if (!host_client->active) + return false; // a command caused an error + + if (msg_badread) + { + Sys_Printf ("SV_ReadClientMessage: badread\n"); + return false; + } + + ccmd = MSG_ReadChar (); + + switch (ccmd) + { + case -1: + goto nextmsg; // end of message + + default: + Sys_Printf ("SV_ReadClientMessage: unknown command char\n"); + return false; + + case clc_nop: +// Sys_Printf ("clc_nop\n"); + break; + + case clc_stringcmd: + s = MSG_ReadString (); + ret = 0; + if (q_strncasecmp(s, "status", 6) == 0) + ret = 1; + else if (q_strncasecmp(s, "god", 3) == 0) + ret = 1; + else if (q_strncasecmp(s, "notarget", 8) == 0) + ret = 1; + else if (q_strncasecmp(s, "fly", 3) == 0) + ret = 1; + else if (q_strncasecmp(s, "name", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "noclip", 6) == 0) + ret = 1; + else if (q_strncasecmp(s, "setpos", 6) == 0) + ret = 1; + else if (q_strncasecmp(s, "say", 3) == 0) + ret = 1; + else if (q_strncasecmp(s, "say_team", 8) == 0) + ret = 1; + else if (q_strncasecmp(s, "tell", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "color", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "kill", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "pause", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "spawn", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "begin", 5) == 0) + ret = 1; + else if (q_strncasecmp(s, "prespawn", 8) == 0) + ret = 1; + else if (q_strncasecmp(s, "kick", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "ping", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "give", 4) == 0) + ret = 1; + else if (q_strncasecmp(s, "ban", 3) == 0) + ret = 1; + + if (ret == 1) + Cmd_ExecuteString (s, src_client); + else + Con_DPrintf("%s tried to %s\n", host_client->name, s); + break; + + case clc_disconnect: + // Sys_Printf ("SV_ReadClientMessage: client disconnected\n"); + return false; + + case clc_move: + SV_ReadClientMove (&host_client->cmd); + break; + } + } + } while (ret == 1); + + return true; +} + + +/* +================== +SV_RunClients +================== +*/ +void SV_RunClients (void) +{ + int i; + + for (i=0, host_client = svs.clients ; iactive) + continue; + + sv_player = host_client->edict; + + if (!SV_ReadClientMessage ()) + { + SV_DropClient (false); // client misbehaved... + continue; + } + + if (!host_client->spawned) + { + // clear client movement until a new packet is received + memset (&host_client->cmd, 0, sizeof(host_client->cmd)); + continue; + } + +// always pause in single player if in console or menus + if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) ) + SV_ClientThink (); + } +} + diff --git a/Quake/sys.hpp b/Quake/sys.hpp new file mode 100644 index 0000000..05d1055 --- /dev/null +++ b/Quake/sys.hpp @@ -0,0 +1,73 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_SYS_H +#define _QUAKE_SYS_H + +// sys.h -- non-portable functions + +void Sys_Init (void); + +// +// file IO +// + +// returns the file size or -1 if file is not present. +// the file should be in BINARY mode for stupid OSs that care +int Sys_FileOpenRead (const char *path, int *hndl); + +int Sys_FileOpenWrite (const char *path); +void Sys_FileClose (int handle); +void Sys_FileSeek (int handle, int position); +int Sys_FileRead (int handle, void *dest, int count); +int Sys_FileWrite (int handle,const void *data, int count); +void Sys_mkdir (const char *path); + +int Sys_FileType (const char *path); +/* returns an FS entity type, i.e. FS_ENT_FILE or FS_ENT_DIRECTORY. + * returns FS_ENT_NONE (0) if no such file or directory is present. */ + +// +// system IO +// +FUNC_NORETURN void Sys_Quit (void); +FUNC_NORETURN void Sys_Error (const char *error, ...) FUNC_PRINTF(1,2); +// an error will cause the entire program to exit +#ifdef __WATCOMC__ +#pragma aux Sys_Error aborts; +#pragma aux Sys_Quit aborts; +#endif + +void Sys_Printf (const char *fmt, ...) FUNC_PRINTF(1,2); +// send text to the console + +double Sys_DoubleTime (void); + +const char *Sys_ConsoleInput (void); + +void Sys_Sleep (unsigned long msecs); +// yield for about 'msecs' milliseconds. + +void Sys_SendKeyEvents (void); +// Perform Key_Event () callbacks until the input que is empty + +#endif /* _QUAKE_SYS_H */ + diff --git a/Quake/sys_sdl_unix.cpp b/Quake/sys_sdl_unix.cpp new file mode 100644 index 0000000..500c0ef --- /dev/null +++ b/Quake/sys_sdl_unix.cpp @@ -0,0 +1,530 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "arch_def.hpp" +#include "quakedef.hpp" + +#include +#include +#include +#if defined(PLATFORM_OSX) || defined(PLATFORM_HAIKU) +#include /* dirname() and basename() */ +#endif +#include +#include +#include +#ifdef DO_USERDIRS +#include +#endif + +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + + +qboolean isDedicated; +cvar_t sys_throttle = {"sys_throttle", "0.02", CVAR_ARCHIVE}; + +#define MAX_HANDLES 32 /* johnfitz -- was 10 */ +static FILE *sys_handles[MAX_HANDLES]; +static qboolean stdinIsATTY; /* from ioquake3 source */ + + +static int findhandle (void) +{ + int i; + + for (i = 1; i < MAX_HANDLES; i++) + { + if (!sys_handles[i]) + return i; + } + Sys_Error ("out of handles"); + return -1; +} + +long Sys_filelength (FILE *f) +{ + long pos, end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +int Sys_FileOpenRead (const char *path, int *hndl) +{ + FILE *f; + int i, retval; + + i = findhandle (); + f = fopen(path, "rb"); + + if (!f) + { + *hndl = -1; + retval = -1; + } + else + { + sys_handles[i] = f; + *hndl = i; + retval = Sys_filelength(f); + } + + return retval; +} + +int Sys_FileOpenWrite (const char *path) +{ + FILE *f; + int i; + + i = findhandle (); + f = fopen(path, "wb"); + + if (!f) + Sys_Error ("Error opening %s: %s", path, strerror(errno)); + + sys_handles[i] = f; + return i; +} + +void Sys_FileClose (int handle) +{ + fclose (sys_handles[handle]); + sys_handles[handle] = NULL; +} + +void Sys_FileSeek (int handle, int position) +{ + fseek (sys_handles[handle], position, SEEK_SET); +} + +int Sys_FileRead (int handle, void *dest, int count) +{ + return fread (dest, 1, count, sys_handles[handle]); +} + +int Sys_FileWrite (int handle, const void *data, int count) +{ + return fwrite (data, 1, count, sys_handles[handle]); +} + +int Sys_FileType (const char *path) +{ + /* + if (access(path, R_OK) == -1) + return 0; + */ + struct stat st; + + if (stat(path, &st) != 0) + return FS_ENT_NONE; + if (S_ISDIR(st.st_mode)) + return FS_ENT_DIRECTORY; + if (S_ISREG(st.st_mode)) + return FS_ENT_FILE; + + return FS_ENT_NONE; +} + + +#if defined(__linux__) || defined(__sun) || defined(sun) || defined(_AIX) +static int Sys_NumCPUs (void) +{ + int numcpus = sysconf(_SC_NPROCESSORS_ONLN); + return (numcpus < 1) ? 1 : numcpus; +} + +#elif defined(PLATFORM_OSX) +#include +#if !defined(HW_AVAILCPU) /* using an ancient SDK? */ +#define HW_AVAILCPU 25 /* needs >= 10.2 */ +#endif +static int Sys_NumCPUs (void) +{ + int numcpus; + int mib[2]; + size_t len; + +#if defined(_SC_NPROCESSORS_ONLN) /* needs >= 10.5 */ + numcpus = sysconf(_SC_NPROCESSORS_ONLN); + if (numcpus != -1) + return (numcpus < 1) ? 1 : numcpus; +#endif + len = sizeof(numcpus); + mib[0] = CTL_HW; + mib[1] = HW_AVAILCPU; + sysctl(mib, 2, &numcpus, &len, NULL, 0); + if (sysctl(mib, 2, &numcpus, &len, NULL, 0) == -1) + { + mib[1] = HW_NCPU; + if (sysctl(mib, 2, &numcpus, &len, NULL, 0) == -1) + return 1; + } + return (numcpus < 1) ? 1 : numcpus; +} + +#elif defined(__sgi) || defined(sgi) || defined(__sgi__) /* IRIX */ +static int Sys_NumCPUs (void) +{ + int numcpus = sysconf(_SC_NPROC_ONLN); + if (numcpus < 1) + numcpus = 1; + return numcpus; +} + +#elif defined(PLATFORM_BSD) +#include +static int Sys_NumCPUs (void) +{ + int numcpus; + int mib[2]; + size_t len; + +#if defined(_SC_NPROCESSORS_ONLN) + numcpus = sysconf(_SC_NPROCESSORS_ONLN); + if (numcpus != -1) + return (numcpus < 1) ? 1 : numcpus; +#endif + len = sizeof(numcpus); + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + if (sysctl(mib, 2, &numcpus, &len, NULL, 0) == -1) + return 1; + return (numcpus < 1) ? 1 : numcpus; +} + +#elif defined(__hpux) || defined(__hpux__) || defined(_hpux) +#include +static int Sys_NumCPUs (void) +{ + int numcpus = mpctl(MPC_GETNUMSPUS, NULL, NULL); + return numcpus; +} + +#else /* unknown OS */ +static int Sys_NumCPUs (void) +{ + return -2; +} +#endif + +static char cwd[MAX_OSPATH]; +#ifdef DO_USERDIRS +static char userdir[MAX_OSPATH]; +#ifdef PLATFORM_OSX +#define SYS_USERDIR "Library/Application Support/QuakeSpasm" +#elif defined(PLATFORM_HAIKU) +#define SYS_USERDIR "QuakeSpasm" +#else +#define SYS_USERDIR ".quakespasm" +#endif + +#ifdef PLATFORM_HAIKU +#include +#include + +static void Sys_GetUserdir (char *dst, size_t dstsize) +{ + dev_t volume = dev_for_path("/boot"); + char buffer[B_PATH_NAME_LENGTH]; + status_t result; + + result = find_directory(B_USER_NONPACKAGED_DATA_DIRECTORY, volume, false, buffer, sizeof(buffer)); + if (result != B_OK) + Sys_Error ("Couldn't determine userspace directory"); + + q_snprintf (dst, dstsize, "%s/%s", buffer, SYS_USERDIR); +} +#else +static void Sys_GetUserdir (char *dst, size_t dstsize) +{ + size_t n; + const char *home_dir = NULL; + struct passwd *pwent; + + pwent = getpwuid( getuid() ); + if (pwent == NULL) + perror("getpwuid"); + else + home_dir = pwent->pw_dir; + if (home_dir == NULL) + home_dir = getenv("HOME"); + if (home_dir == NULL) + Sys_Error ("Couldn't determine userspace directory"); + +/* what would be a maximum path for a file in the user's directory... + * $HOME/SYS_USERDIR/game_dir/dirname1/dirname2/dirname3/filename.ext + * still fits in the MAX_OSPATH == 256 definition, but just in case : + */ + n = strlen(home_dir) + strlen(SYS_USERDIR) + 50; + if (n >= dstsize) + Sys_Error ("Insufficient array size for userspace directory"); + + q_snprintf (dst, dstsize, "%s/%s", home_dir, SYS_USERDIR); +} +#endif /* PLATFORM_HAIKU */ +#endif /* DO_USERDIRS */ + +#ifdef PLATFORM_OSX +static char *OSX_StripAppBundle (char *dir) +{ /* based on the ioquake3 project at icculus.org. */ + static char osx_path[MAX_OSPATH]; + + q_strlcpy (osx_path, dir, sizeof(osx_path)); + if (strcmp(basename(osx_path), "MacOS")) + return dir; + q_strlcpy (osx_path, dirname(osx_path), sizeof(osx_path)); + if (strcmp(basename(osx_path), "Contents")) + return dir; + q_strlcpy (osx_path, dirname(osx_path), sizeof(osx_path)); + if (!strstr(basename(osx_path), ".app")) + return dir; + q_strlcpy (osx_path, dirname(osx_path), sizeof(osx_path)); + return osx_path; +} + +static void Sys_GetBasedir (char *argv0, char *dst, size_t dstsize) +{ + char *tmp; + + if (realpath(argv0, dst) == NULL) + { + perror("realpath"); + if (getcwd(dst, dstsize - 1) == NULL) + _fail: Sys_Error ("Couldn't determine current directory"); + } + else + { + /* strip off the binary name */ + if (! (tmp = strdup (dst))) goto _fail; + q_strlcpy (dst, dirname(tmp), dstsize); + free (tmp); + } + + tmp = OSX_StripAppBundle(dst); + if (tmp != dst) + q_strlcpy (dst, tmp, dstsize); +} +#else +static void Sys_GetBasedir (char *argv0, char *dst, size_t dstsize) +{ + char *tmp; + + #ifdef PLATFORM_HAIKU + if (realpath(argv0, dst) == NULL) + { + perror("realpath"); + if (getcwd(dst, dstsize - 1) == NULL) + _fail: Sys_Error ("Couldn't determine current directory"); + } + else + { + /* strip off the binary name */ + if (! (tmp = strdup (dst))) goto _fail; + q_strlcpy (dst, dirname(tmp), dstsize); + free (tmp); + } + #else + if (getcwd(dst, dstsize - 1) == NULL) + Sys_Error ("Couldn't determine current directory"); + + tmp = dst; + while (*tmp != 0) + tmp++; + while (*tmp == 0 && tmp != dst) + { + --tmp; + if (tmp != dst && *tmp == '/') + *tmp = 0; + } + #endif +} +#endif + +void Sys_Init (void) +{ + const char* term = getenv("TERM"); + stdinIsATTY = isatty(STDIN_FILENO) && + !(term && (!strcmp(term, "raw") || !strcmp(term, "dumb"))); + if (!stdinIsATTY) + Sys_Printf("Terminal input not available.\n"); + + memset (cwd, 0, sizeof(cwd)); + Sys_GetBasedir(host_parms->argv[0], cwd, sizeof(cwd)); + host_parms->basedir = cwd; +#ifndef DO_USERDIRS + host_parms->userdir = host_parms->basedir; /* code elsewhere relies on this ! */ +#else + memset (userdir, 0, sizeof(userdir)); + Sys_GetUserdir(userdir, sizeof(userdir)); + Sys_mkdir (userdir); + host_parms->userdir = userdir; +#endif + host_parms->numcpus = Sys_NumCPUs (); + Sys_Printf("Detected %d CPUs.\n", host_parms->numcpus); +} + +void Sys_mkdir (const char *path) +{ + int rc = mkdir (path, 0777); + if (rc != 0 && errno == EEXIST) + { + struct stat st; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + rc = 0; + } + if (rc != 0) + { + rc = errno; + Sys_Error("Unable to create directory %s: %s", path, strerror(rc)); + } +} + +static const char errortxt1[] = "\nERROR-OUT BEGIN\n\n"; +static const char errortxt2[] = "\nQUAKE ERROR: "; + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char text[1024]; + + host_parms->errstate++; + + va_start (argptr, error); + q_vsnprintf (text, sizeof(text), error, argptr); + va_end (argptr); + + fputs (errortxt1, stderr); + Host_Shutdown (); + fputs (errortxt2, stderr); + fputs (text, stderr); + fputs ("\n\n", stderr); + if (!isDedicated) + PL_ErrorDialog(text); + + exit (1); +} + +void Sys_Printf (const char *fmt, ...) +{ + va_list argptr; + + va_start(argptr, fmt); + vprintf(fmt, argptr); + va_end(argptr); +} + +void Sys_Quit (void) +{ + Host_Shutdown(); + + exit (0); +} + +double Sys_DoubleTime (void) +{ + return SDL_GetTicks() / 1000.0; +} + +const char *Sys_ConsoleInput (void) +{ + static qboolean con_eof = false; + static char con_text[256]; + static int textlen; + char c; + fd_set set; + struct timeval timeout; + + if (!stdinIsATTY || con_eof) + return NULL; + + FD_ZERO (&set); + FD_SET (0, &set); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + while (select (1, &set, NULL, NULL, &timeout)) + { + if (read(0, &c, 1) <= 0) + { + // Finish processing whatever is already in the + // buffer (if anything), then stop reading + con_eof = true; + c = '\n'; + } + if (c == '\n' || c == '\r') + { + con_text[textlen] = '\0'; + textlen = 0; + return con_text; + } + else if (c == 8) + { + if (textlen) + { + textlen--; + con_text[textlen] = '\0'; + } + continue; + } + con_text[textlen] = c; + textlen++; + if (textlen < (int) sizeof(con_text)) + con_text[textlen] = '\0'; + else + { + // buffer is full + textlen = 0; + con_text[0] = '\0'; + Sys_Printf("\nConsole input too long!\n"); + break; + } + } + + return NULL; +} + +void Sys_Sleep (unsigned long msecs) +{ +/* usleep (msecs * 1000);*/ + SDL_Delay (msecs); +} + +void Sys_SendKeyEvents (void) +{ + IN_Commands(); //ericw -- allow joysticks to add keys so they can be used to confirm SCR_ModalMessage + IN_SendKeyEvents(); +} + diff --git a/Quake/sys_sdl_win.cpp b/Quake/sys_sdl_win.cpp new file mode 100644 index 0000000..2f6f736 --- /dev/null +++ b/Quake/sys_sdl_win.cpp @@ -0,0 +1,443 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2005 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +#include "quakedef.h" + +#include +#include +#include +#include + +#if defined(SDL_FRAMEWORK) || defined(NO_SDL_CONFIG) +#if defined(USE_SDL2) +#include +#else +#include +#endif +#else +#include "SDL.h" +#endif + + +qboolean isDedicated; +qboolean Win95, Win95old, WinNT, WinVista; +cvar_t sys_throttle = {"sys_throttle", "0.02", CVAR_ARCHIVE}; + +static HANDLE hinput, houtput; + +#define MAX_HANDLES 32 /* johnfitz -- was 10 */ +static FILE *sys_handles[MAX_HANDLES]; + + +static int findhandle (void) +{ + int i; + + for (i = 1; i < MAX_HANDLES; i++) + { + if (!sys_handles[i]) + return i; + } + Sys_Error ("out of handles"); + return -1; +} + +long Sys_filelength (FILE *f) +{ + long pos, end; + + pos = ftell (f); + fseek (f, 0, SEEK_END); + end = ftell (f); + fseek (f, pos, SEEK_SET); + + return end; +} + +int Sys_FileOpenRead (const char *path, int *hndl) +{ + FILE *f; + int i, retval; + + i = findhandle (); + f = fopen(path, "rb"); + + if (!f) + { + *hndl = -1; + retval = -1; + } + else + { + sys_handles[i] = f; + *hndl = i; + retval = Sys_filelength(f); + } + + return retval; +} + +int Sys_FileOpenWrite (const char *path) +{ + FILE *f; + int i; + + i = findhandle (); + f = fopen(path, "wb"); + + if (!f) + Sys_Error ("Error opening %s: %s", path, strerror(errno)); + + sys_handles[i] = f; + return i; +} + +void Sys_FileClose (int handle) +{ + fclose (sys_handles[handle]); + sys_handles[handle] = NULL; +} + +void Sys_FileSeek (int handle, int position) +{ + fseek (sys_handles[handle], position, SEEK_SET); +} + +int Sys_FileRead (int handle, void *dest, int count) +{ + return fread (dest, 1, count, sys_handles[handle]); +} + +int Sys_FileWrite (int handle, const void *data, int count) +{ + return fwrite (data, 1, count, sys_handles[handle]); +} + +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif +int Sys_FileType (const char *path) +{ + DWORD result = GetFileAttributes(path); + + if (result == INVALID_FILE_ATTRIBUTES) + return FS_ENT_NONE; + if (result & FILE_ATTRIBUTE_DIRECTORY) + return FS_ENT_DIRECTORY; + + return FS_ENT_FILE; +} + +static char cwd[1024]; + +static void Sys_GetBasedir (char *argv0, char *dst, size_t dstsize) +{ + char *tmp; + size_t rc; + + rc = GetCurrentDirectory(dstsize, dst); + if (rc == 0 || rc > dstsize) + Sys_Error ("Couldn't determine current directory"); + + tmp = dst; + while (*tmp != 0) + tmp++; + while (*tmp == 0 && tmp != dst) + { + --tmp; + if (tmp != dst && (*tmp == '/' || *tmp == '\\')) + *tmp = 0; + } +} + +typedef enum { dpi_unaware = 0, dpi_system_aware = 1, dpi_monitor_aware = 2 } dpi_awareness; +typedef BOOL (WINAPI *SetProcessDPIAwareFunc)(); +typedef HRESULT (WINAPI *SetProcessDPIAwarenessFunc)(dpi_awareness value); + +static void Sys_SetDPIAware (void) +{ + HMODULE hUser32, hShcore; + SetProcessDPIAwarenessFunc setDPIAwareness; + SetProcessDPIAwareFunc setDPIAware; + + /* Neither SDL 1.2 nor SDL 2.0.3 can handle the OS scaling our window. + (e.g. https://bugzilla.libsdl.org/show_bug.cgi?id=2713) + Call SetProcessDpiAwareness/SetProcessDPIAware to opt out of scaling. + */ + + hShcore = LoadLibraryA ("Shcore.dll"); + hUser32 = LoadLibraryA ("user32.dll"); + setDPIAwareness = (SetProcessDPIAwarenessFunc) (hShcore ? GetProcAddress (hShcore, "SetProcessDpiAwareness") : NULL); + setDPIAware = (SetProcessDPIAwareFunc) (hUser32 ? GetProcAddress (hUser32, "SetProcessDPIAware") : NULL); + + if (setDPIAwareness) /* Windows 8.1+ */ + setDPIAwareness (dpi_monitor_aware); + else if (setDPIAware) /* Windows Vista-8.0 */ + setDPIAware (); + + if (hShcore) + FreeLibrary (hShcore); + if (hUser32) + FreeLibrary (hUser32); +} + +static void Sys_SetTimerResolution(void) +{ + /* Set OS timer resolution to 1ms. + Works around buffer underruns with directsound and SDL2, but also + will make Sleep()/SDL_Dleay() accurate to 1ms which should help framerate + stability. + */ + timeBeginPeriod (1); +} + +void Sys_Init (void) +{ + OSVERSIONINFO vinfo; + + Sys_SetTimerResolution (); + Sys_SetDPIAware (); + + memset (cwd, 0, sizeof(cwd)); + Sys_GetBasedir(NULL, cwd, sizeof(cwd)); + host_parms->basedir = cwd; + + /* userdirs not really necessary for windows guys. + * can be done if necessary, though... */ + host_parms->userdir = host_parms->basedir; /* code elsewhere relies on this ! */ + + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + + if (!GetVersionEx (&vinfo)) + Sys_Error ("Couldn't get OS info"); + + if ((vinfo.dwMajorVersion < 4) || + (vinfo.dwPlatformId == VER_PLATFORM_WIN32s)) + { + Sys_Error ("QuakeSpasm requires at least Win95 or NT 4.0"); + } + + if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + SYSTEM_INFO info; + WinNT = true; + if (vinfo.dwMajorVersion >= 6) + WinVista = true; + GetSystemInfo(&info); + host_parms->numcpus = info.dwNumberOfProcessors; + if (host_parms->numcpus < 1) + host_parms->numcpus = 1; + } + else + { + WinNT = false; /* Win9x or WinME */ + host_parms->numcpus = 1; + if ((vinfo.dwMajorVersion == 4) && (vinfo.dwMinorVersion == 0)) + { + Win95 = true; + /* Win95-gold or Win95A can't switch bpp automatically */ + if (vinfo.szCSDVersion[1] != 'C' && vinfo.szCSDVersion[1] != 'B') + Win95old = true; + } + } + Sys_Printf("Detected %d CPUs.\n", host_parms->numcpus); + + if (isDedicated) + { + if (!AllocConsole ()) + { + isDedicated = false; /* so that we have a graphical error dialog */ + Sys_Error ("Couldn't create dedicated server console"); + } + + hinput = GetStdHandle (STD_INPUT_HANDLE); + houtput = GetStdHandle (STD_OUTPUT_HANDLE); + } +} + +void Sys_mkdir (const char *path) +{ + if (CreateDirectory(path, NULL) != 0) + return; + if (GetLastError() != ERROR_ALREADY_EXISTS) + Sys_Error("Unable to create directory %s", path); +} + +static const char errortxt1[] = "\nERROR-OUT BEGIN\n\n"; +static const char errortxt2[] = "\nQUAKE ERROR: "; + +void Sys_Error (const char *error, ...) +{ + va_list argptr; + char text[1024]; + DWORD dummy; + + host_parms->errstate++; + + va_start (argptr, error); + q_vsnprintf (text, sizeof(text), error, argptr); + va_end (argptr); + + if (isDedicated) + WriteFile (houtput, errortxt1, strlen(errortxt1), &dummy, NULL); + /* SDL will put these into its own stderr log, + so print to stderr even in graphical mode. */ + fputs (errortxt1, stderr); + Host_Shutdown (); + fputs (errortxt2, stderr); + fputs (text, stderr); + fputs ("\n\n", stderr); + if (!isDedicated) + PL_ErrorDialog(text); + else + { + WriteFile (houtput, errortxt2, strlen(errortxt2), &dummy, NULL); + WriteFile (houtput, text, strlen(text), &dummy, NULL); + WriteFile (houtput, "\r\n", 2, &dummy, NULL); + SDL_Delay (3000); /* show the console 3 more seconds */ + } + + exit (1); +} + +void Sys_Printf (const char *fmt, ...) +{ + va_list argptr; + char text[1024]; + DWORD dummy; + + va_start (argptr,fmt); + q_vsnprintf (text, sizeof(text), fmt, argptr); + va_end (argptr); + + if (isDedicated) + { + WriteFile(houtput, text, strlen(text), &dummy, NULL); + } + else + { + /* SDL will put these into its own stdout log, + so print to stdout even in graphical mode. */ + fputs (text, stdout); + } +} + +void Sys_Quit (void) +{ + Host_Shutdown(); + + if (isDedicated) + FreeConsole (); + + exit (0); +} + +double Sys_DoubleTime (void) +{ + return SDL_GetTicks() / 1000.0; +} + +const char *Sys_ConsoleInput (void) +{ + static char con_text[256]; + static int textlen; + INPUT_RECORD recs[1024]; + int ch; + DWORD dummy, numread, numevents; + + for ( ;; ) + { + if (GetNumberOfConsoleInputEvents(hinput, &numevents) == 0) + Sys_Error ("Error getting # of console events"); + + if (! numevents) + break; + + if (ReadConsoleInput(hinput, recs, 1, &numread) == 0) + Sys_Error ("Error reading console input"); + + if (numread != 1) + Sys_Error ("Couldn't read console input"); + + if (recs[0].EventType == KEY_EVENT) + { + if (recs[0].Event.KeyEvent.bKeyDown == FALSE) + { + ch = recs[0].Event.KeyEvent.uChar.AsciiChar; + + switch (ch) + { + case '\r': + WriteFile(houtput, "\r\n", 2, &dummy, NULL); + + if (textlen != 0) + { + con_text[textlen] = 0; + textlen = 0; + return con_text; + } + + break; + + case '\b': + WriteFile(houtput, "\b \b", 3, &dummy, NULL); + if (textlen != 0) + textlen--; + + break; + + default: + if (ch >= ' ') + { + WriteFile(houtput, &ch, 1, &dummy, NULL); + con_text[textlen] = ch; + textlen = (textlen + 1) & 0xff; + } + + break; + } + } + } + } + + return NULL; +} + +void Sys_Sleep (unsigned long msecs) +{ +/* Sleep (msecs);*/ + SDL_Delay (msecs); +} + +void Sys_SendKeyEvents (void) +{ + IN_Commands(); //ericw -- allow joysticks to add keys so they can be used to confirm SCR_ModalMessage + IN_SendKeyEvents(); +} + diff --git a/Quake/vid.hpp b/Quake/vid.hpp new file mode 100644 index 0000000..46ae8d6 --- /dev/null +++ b/Quake/vid.hpp @@ -0,0 +1,93 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __VID_DEFS_H +#define __VID_DEFS_H + +// vid.h -- video driver defs + +#define VID_CBITS 6 +#define VID_GRADES (1 << VID_CBITS) + +#define GAMMA_MAX 3.0 + +// moved here for global use -- kristian +typedef enum { MS_UNINIT, MS_WINDOWED, MS_FULLSCREEN } modestate_t; + +extern modestate_t modestate; + +// a pixel can be one, two, or four bytes +typedef byte pixel_t; + +typedef struct vrect_s +{ + int x, y, width, height; + struct vrect_s *pnext; +} vrect_t; + +typedef struct +{ + pixel_t *buffer; // invisible buffer + pixel_t *colormap; // 256 * VID_GRADES size + unsigned short *colormap16; // 256 * VID_GRADES size + int fullbright; // index of first fullbright color + int rowbytes; // may be > width if displayed in a window + int width; + int height; + float aspect; // width / height -- < 0 is taller than wide + int numpages; + int recalc_refdef; // if true, recalc vid-based stuff + pixel_t *conbuffer; + int conrowbytes; + int conwidth; + int conheight; + int maxwarpwidth; + int maxwarpheight; + pixel_t *direct; // direct drawing to framebuffer, if not NULL +} viddef_t; + +extern viddef_t vid; // global video state + +extern void (*vid_menudrawfn)(void); +extern void (*vid_menukeyfn)(int key); +extern void (*vid_menucmdfn)(void); //johnfitz + +void VID_Init (void); //johnfitz -- removed palette from argument list + +void VID_Shutdown (void); +// Called at shutdown + +void VID_Update (vrect_t *rects); +// flushes the given rectangles from the view buffer to the screen + +void VID_SyncCvars (void); + +void VID_Toggle (void); + +void *VID_GetWindow (void); +qboolean VID_HasMouseOrInputFocus (void); +qboolean VID_IsMinimized (void); +void VID_Lock (void); + +#endif /* __VID_DEFS_H */ + diff --git a/Quake/view.cpp b/Quake/view.cpp new file mode 100644 index 0000000..1f84e72 --- /dev/null +++ b/Quake/view.cpp @@ -0,0 +1,975 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// view.c -- player eye positioning + +#include "quakedef.hpp" + +/* + +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. + +*/ + +cvar_t scr_ofsx = {"scr_ofsx","0", CVAR_NONE}; +cvar_t scr_ofsy = {"scr_ofsy","0", CVAR_NONE}; +cvar_t scr_ofsz = {"scr_ofsz","0", CVAR_NONE}; + +cvar_t cl_rollspeed = {"cl_rollspeed", "200", CVAR_NONE}; +cvar_t cl_rollangle = {"cl_rollangle", "2.0", CVAR_NONE}; + +cvar_t cl_bob = {"cl_bob","0.02", CVAR_NONE}; +cvar_t cl_bobcycle = {"cl_bobcycle","0.6", CVAR_NONE}; +cvar_t cl_bobup = {"cl_bobup","0.5", CVAR_NONE}; + +cvar_t v_kicktime = {"v_kicktime", "0.5", CVAR_NONE}; +cvar_t v_kickroll = {"v_kickroll", "0.6", CVAR_NONE}; +cvar_t v_kickpitch = {"v_kickpitch", "0.6", CVAR_NONE}; +cvar_t v_gunkick = {"v_gunkick", "1", CVAR_NONE}; //johnfitz + +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", CVAR_NONE}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", CVAR_NONE}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", CVAR_NONE}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", CVAR_NONE}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", CVAR_NONE}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", CVAR_NONE}; + +cvar_t v_idlescale = {"v_idlescale", "0", CVAR_NONE}; + +cvar_t crosshair = {"crosshair", "0", CVAR_ARCHIVE}; + +cvar_t gl_cshiftpercent = {"gl_cshiftpercent", "100", CVAR_NONE}; +cvar_t gl_cshiftpercent_contents = {"gl_cshiftpercent_contents", "100", CVAR_NONE}; // QuakeSpasm +cvar_t gl_cshiftpercent_damage = {"gl_cshiftpercent_damage", "100", CVAR_NONE}; // QuakeSpasm +cvar_t gl_cshiftpercent_bonus = {"gl_cshiftpercent_bonus", "100", CVAR_NONE}; // QuakeSpasm +cvar_t gl_cshiftpercent_powerup = {"gl_cshiftpercent_powerup", "100", CVAR_NONE}; // QuakeSpasm + +cvar_t r_viewmodel_quake = {"r_viewmodel_quake", "0", CVAR_ARCHIVE}; + +vec3_t v_punchangles[2]; //johnfitz -- copied from cl.punchangle. 0 is current, 1 is previous value. never the same unless map just loaded + +/* +=============== +V_CalcRoll + +Used by view and sv_user +=============== +*/ +float V_CalcRoll (vec3_t angles, vec3_t velocity) +{ + vec3_t forward, right, up; + float sign; + float side; + float value; + + AngleVectors (angles, forward, right, up); + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = cl_rollangle.value; +// if (cl.inwater) +// value *= 6; + + if (side < cl_rollspeed.value) + side = side * value / cl_rollspeed.value; + else + side = value; + + return side*sign; +} + + +/* +=============== +V_CalcBob + +=============== +*/ +float V_CalcBob (void) +{ + float bob; + float cycle; + + if (!cl_bobcycle.value) /* Avoid divide-by-zero, don't bob */ + return 0.0f; + + cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value; + cycle /= cl_bobcycle.value; + if (cycle < cl_bobup.value) + cycle = M_PI * cycle / cl_bobup.value; + else + cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value); + +// bob is proportional to velocity in the xy plane +// (don't count Z, or jumping messes it up) + + bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value; +//Con_Printf ("speed: %5.1f\n", VectorLength(cl.velocity)); + bob = bob*0.3 + bob*0.7*sin(cycle); + if (bob > 4) + bob = 4; + else if (bob < -7) + bob = -7; + return bob; +} + + +//============================================================================= + + +cvar_t v_centermove = {"v_centermove", "0.15", CVAR_NONE}; +cvar_t v_centerspeed = {"v_centerspeed","500", CVAR_NONE}; + + +void V_StartPitchDrift (void) +{ +#if 1 + if (cl.laststop == cl.time) + { + return; // something else is keeping it from drifting + } +#endif + if (cl.nodrift || !cl.pitchvel) + { + cl.pitchvel = v_centerspeed.value; + cl.nodrift = false; + cl.driftmove = 0; + } +} + +void V_StopPitchDrift (void) +{ + cl.laststop = cl.time; + cl.nodrift = true; + cl.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards cl.idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. + +Drifting is enabled when the center view key is hit, mlook is released and +lookspring is non 0, or when +=============== +*/ +void V_DriftPitch (void) +{ + float delta, move; + + if (noclip_anglehack || !cl.onground || cls.demoplayback ) + //FIXME: noclip_anglehack is set on the server, so in a nonlocal game this won't work. + { + cl.driftmove = 0; + cl.pitchvel = 0; + return; + } + +// don't count small mouse motion + if (cl.nodrift) + { + if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value) + cl.driftmove = 0; + else + cl.driftmove += host_frametime; + + if ( cl.driftmove > v_centermove.value) + { + if (lookspring.value) + V_StartPitchDrift (); + } + return; + } + + delta = cl.idealpitch - cl.viewangles[PITCH]; + + if (!delta) + { + cl.pitchvel = 0; + return; + } + + move = host_frametime * cl.pitchvel; + cl.pitchvel += host_frametime * v_centerspeed.value; + +//Con_Printf ("move: %f (%f)\n", move, host_frametime); + + if (delta > 0) + { + if (move > delta) + { + cl.pitchvel = 0; + move = delta; + } + cl.viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + cl.pitchvel = 0; + move = -delta; + } + cl.viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + + VIEW BLENDING + +============================================================================== +*/ + +static cshift_t cshift_empty = { {130,80,50}, 0 }; +static cshift_t cshift_water = { {130,80,50}, 128 }; +static cshift_t cshift_slime = { {0,25,5}, 150 }; +static cshift_t cshift_lava = { {255,80,0}, 150 }; + +static float v_dmg_time, v_dmg_roll, v_dmg_pitch; + +float v_blend[4]; // rgba 0.0 - 1.0 + +//johnfitz -- deleted BuildGammaTable(), V_CheckGamma(), gammatable[], and ramps[][] + +/* +=============== +V_ParseDamage +=============== +*/ +void V_ParseDamage (void) +{ + int armor, blood; + vec3_t from; + int i; + vec3_t forward, right, up; + entity_t *ent; + float side; + float count; + + armor = MSG_ReadByte (); + blood = MSG_ReadByte (); + for (i=0 ; i<3 ; i++) + from[i] = MSG_ReadCoord (cl.protocolflags); + + count = blood*0.5 + armor*0.5; + if (count < 10) + count = 10; + + cl.faceanimtime = cl.time + 0.2; // but sbar face into pain frame + + cl.cshifts[CSHIFT_DAMAGE].percent += 3*count; + if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) + cl.cshifts[CSHIFT_DAMAGE].percent = 0; + if (cl.cshifts[CSHIFT_DAMAGE].percent > 150) + cl.cshifts[CSHIFT_DAMAGE].percent = 150; + + if (armor > blood) + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100; + } + else if (armor) + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50; + } + else + { + cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255; + cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0; + cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0; + } + +// +// calculate view angle kicks +// + ent = &cl_entities[cl.viewentity]; + + VectorSubtract (from, ent->origin, from); + VectorNormalize (from); + + AngleVectors (ent->angles, forward, right, up); + + side = DotProduct (from, right); + v_dmg_roll = count*side*v_kickroll.value; + + side = DotProduct (from, forward); + v_dmg_pitch = count*side*v_kickpitch.value; + + v_dmg_time = v_kicktime.value; +} + + +/* +================== +V_cshift_f +================== +*/ +void V_cshift_f (void) +{ + cshift_empty.destcolor[0] = atoi(Cmd_Argv(1)); + cshift_empty.destcolor[1] = atoi(Cmd_Argv(2)); + cshift_empty.destcolor[2] = atoi(Cmd_Argv(3)); + cshift_empty.percent = atoi(Cmd_Argv(4)); +} + + +/* +================== +V_BonusFlash_f + +When you run over an item, the server sends this command +================== +*/ +void V_BonusFlash_f (void) +{ + cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215; + cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186; + cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69; + cl.cshifts[CSHIFT_BONUS].percent = 50; +} + +/* +============= +V_SetContentsColor + +Underwater, lava, etc each has a color shift +============= +*/ +void V_SetContentsColor (int contents) +{ + switch (contents) + { + case CONTENTS_EMPTY: + case CONTENTS_SOLID: + case CONTENTS_SKY: //johnfitz -- no blend in sky + cl.cshifts[CSHIFT_CONTENTS] = cshift_empty; + break; + case CONTENTS_LAVA: + cl.cshifts[CSHIFT_CONTENTS] = cshift_lava; + break; + case CONTENTS_SLIME: + cl.cshifts[CSHIFT_CONTENTS] = cshift_slime; + break; + default: + cl.cshifts[CSHIFT_CONTENTS] = cshift_water; + } +} + +/* +============= +V_CalcPowerupCshift +============= +*/ +void V_CalcPowerupCshift (void) +{ + if (cl.items & IT_QUAD) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255; + cl.cshifts[CSHIFT_POWERUP].percent = 30; + } + else if (cl.items & IT_SUIT) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + cl.cshifts[CSHIFT_POWERUP].percent = 20; + } + else if (cl.items & IT_INVISIBILITY) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100; + cl.cshifts[CSHIFT_POWERUP].percent = 100; + } + else if (cl.items & IT_INVULNERABILITY) + { + cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; + cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; + cl.cshifts[CSHIFT_POWERUP].percent = 30; + } + else + cl.cshifts[CSHIFT_POWERUP].percent = 0; +} + +/* +============= +V_CalcBlend +============= +*/ +void V_CalcBlend (void) +{ + float r, g, b, a, a2; + int j; + cvar_t *cshiftpercent_cvars[NUM_CSHIFTS] = { + &gl_cshiftpercent_contents, + &gl_cshiftpercent_damage, + &gl_cshiftpercent_bonus, + &gl_cshiftpercent_powerup + }; + + r = 0; + g = 0; + b = 0; + a = 0; + + for (j=0 ; jvalue / 100.0); + // QuakeSpasm + if (!a2) + continue; + a = a + a2*(1-a); + a2 = a2/a; + r = r*(1-a2) + cl.cshifts[j].destcolor[0]*a2; + g = g*(1-a2) + cl.cshifts[j].destcolor[1]*a2; + b = b*(1-a2) + cl.cshifts[j].destcolor[2]*a2; + } + + v_blend[0] = r/255.0; + v_blend[1] = g/255.0; + v_blend[2] = b/255.0; + v_blend[3] = a; + if (v_blend[3] > 1) + v_blend[3] = 1; + if (v_blend[3] < 0) + v_blend[3] = 0; +} + +/* +============= +V_UpdateBlend -- johnfitz -- V_UpdatePalette cleaned up and renamed +============= +*/ +void V_UpdateBlend (void) +{ + int i, j; + qboolean blend_changed; + + V_CalcPowerupCshift (); + + blend_changed = false; + + for (i=0 ; i 180) + a -= 360; + return a; +} + +/* +================== +CalcGunAngle +================== +*/ +void CalcGunAngle (void) +{ + float yaw, pitch, move; + static float oldyaw = 0; + static float oldpitch = 0; + + yaw = r_refdef.viewangles[YAW]; + pitch = -r_refdef.viewangles[PITCH]; + + yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4; + if (yaw > 10) + yaw = 10; + if (yaw < -10) + yaw = -10; + pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4; + if (pitch > 10) + pitch = 10; + if (pitch < -10) + pitch = -10; + move = host_frametime*20; + if (yaw > oldyaw) + { + if (oldyaw + move < yaw) + yaw = oldyaw + move; + } + else + { + if (oldyaw - move > yaw) + yaw = oldyaw - move; + } + + if (pitch > oldpitch) + { + if (oldpitch + move < pitch) + pitch = oldpitch + move; + } + else + { + if (oldpitch - move > pitch) + pitch = oldpitch - move; + } + + oldyaw = yaw; + oldpitch = pitch; + + cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw; + cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch); + + cl.viewent.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; + cl.viewent.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; + cl.viewent.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + +/* +============== +V_BoundOffsets +============== +*/ +void V_BoundOffsets (void) +{ + entity_t *ent; + + ent = &cl_entities[cl.viewentity]; + +// absolutely bound refresh reletive to entity clipping hull +// so the view can never be inside a solid wall + + if (r_refdef.vieworg[0] < ent->origin[0] - 14) + r_refdef.vieworg[0] = ent->origin[0] - 14; + else if (r_refdef.vieworg[0] > ent->origin[0] + 14) + r_refdef.vieworg[0] = ent->origin[0] + 14; + if (r_refdef.vieworg[1] < ent->origin[1] - 14) + r_refdef.vieworg[1] = ent->origin[1] - 14; + else if (r_refdef.vieworg[1] > ent->origin[1] + 14) + r_refdef.vieworg[1] = ent->origin[1] + 14; + if (r_refdef.vieworg[2] < ent->origin[2] - 22) + r_refdef.vieworg[2] = ent->origin[2] - 22; + else if (r_refdef.vieworg[2] > ent->origin[2] + 30) + r_refdef.vieworg[2] = ent->origin[2] + 30; +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle (void) +{ + r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; + r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; + r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll (void) +{ + float side; + + side = V_CalcRoll (cl_entities[cl.viewentity].angles, cl.velocity); + r_refdef.viewangles[ROLL] += side; + + if (v_dmg_time > 0) + { + r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll; + r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch; + v_dmg_time -= host_frametime; + } + + if (cl.stats[STAT_HEALTH] <= 0) + { + r_refdef.viewangles[ROLL] = 80; // dead view angle + return; + } +} + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef (void) +{ + entity_t *ent, *view; + float old; + +// ent is the player model (visible when out of body) + ent = &cl_entities[cl.viewentity]; +// view is the weapon model (only visible from inside body) + view = &cl.viewent; + + VectorCopy (ent->origin, r_refdef.vieworg); + VectorCopy (ent->angles, r_refdef.viewangles); + view->model = NULL; + +// allways idle in intermission + old = v_idlescale.value; + v_idlescale.value = 1; + V_AddIdle (); + v_idlescale.value = old; +} + +/* +================== +V_CalcRefdef +================== +*/ +void V_CalcRefdef (void) +{ + entity_t *ent, *view; + int i; + vec3_t forward, right, up; + vec3_t angles; + float bob; + static float oldz = 0; + static vec3_t punch = {0,0,0}; //johnfitz -- v_gunkick + float delta; //johnfitz -- v_gunkick + + V_DriftPitch (); + +// ent is the player model (visible when out of body) + ent = &cl_entities[cl.viewentity]; +// view is the weapon model (only visible from inside body) + view = &cl.viewent; + + +// transform the view offset by the model's matrix to get the offset from +// model origin for the view + ent->angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir + ent->angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir + + bob = V_CalcBob (); + +// refresh position + VectorCopy (ent->origin, r_refdef.vieworg); + r_refdef.vieworg[2] += cl.viewheight + bob; + +// never let it sit exactly on a node line, because a water plane can +// dissapear when viewed with the eye exactly on it. +// the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + r_refdef.vieworg[0] += 1.0/32; + r_refdef.vieworg[1] += 1.0/32; + r_refdef.vieworg[2] += 1.0/32; + + VectorCopy (cl.viewangles, r_refdef.viewangles); + V_CalcViewRoll (); + V_AddIdle (); + +// offsets + angles[PITCH] = -ent->angles[PITCH]; // because entity pitches are actually backward + angles[YAW] = ent->angles[YAW]; + angles[ROLL] = ent->angles[ROLL]; + + AngleVectors (angles, forward, right, up); + + if (cl.maxclients <= 1) //johnfitz -- moved cheat-protection here from V_RenderView + for (i=0 ; i<3 ; i++) + r_refdef.vieworg[i] += scr_ofsx.value*forward[i] + scr_ofsy.value*right[i] + scr_ofsz.value*up[i]; + + V_BoundOffsets (); + +// set up gun position + VectorCopy (cl.viewangles, view->angles); + + CalcGunAngle (); + + VectorCopy (ent->origin, view->origin); + view->origin[2] += cl.viewheight; + + for (i=0 ; i<3 ; i++) + view->origin[i] += forward[i]*bob*0.4; + view->origin[2] += bob; + + //johnfitz -- removed all gun position fudging code (was used to keep gun from getting covered by sbar) + //MarkV -- restored this with r_viewmodel_quake cvar + if (r_viewmodel_quake.value) + { + if (scr_viewsize.value == 110) + view->origin[2] += 1; + else if (scr_viewsize.value == 100) + view->origin[2] += 2; + else if (scr_viewsize.value == 90) + view->origin[2] += 1; + else if (scr_viewsize.value == 80) + view->origin[2] += 0.5; + } + + if (ent->lerpflags & LERP_FINISH) + { + view->lerpflags |= LERP_FINISH; + view->lerpfinish = ent->lerpfinish; + } + else + view->lerpflags &= ~LERP_FINISH; + + view->model = cl.model_precache[cl.stats[STAT_WEAPON]]; + view->frame = cl.stats[STAT_WEAPONFRAME]; + view->colormap = vid.colormap; + view->scale = ENTSCALE_DEFAULT; + +//johnfitz -- v_gunkick + if (v_gunkick.value == 1) //original quake kick + VectorAdd (r_refdef.viewangles, cl.punchangle, r_refdef.viewangles); + if (v_gunkick.value == 2) //lerped kick + { + for (i=0; i<3; i++) + if (punch[i] != v_punchangles[0][i]) + { + //speed determined by how far we need to lerp in 1/10th of a second + delta = (v_punchangles[0][i]-v_punchangles[1][i]) * host_frametime * 10; + + if (delta > 0) + punch[i] = q_min(punch[i]+delta, v_punchangles[0][i]); + else if (delta < 0) + punch[i] = q_max(punch[i]+delta, v_punchangles[0][i]); + } + + VectorAdd (r_refdef.viewangles, punch, r_refdef.viewangles); + } +//johnfitz + +// smooth out stair step ups + if (!noclip_anglehack && cl.onground && ent->origin[2] - oldz > 0) //johnfitz -- added exception for noclip + //FIXME: noclip_anglehack is set on the server, so in a nonlocal game this won't work. + { + float steptime; + + steptime = cl.time - cl.oldtime; + if (steptime < 0) + //FIXME I_Error ("steptime < 0"); + steptime = 0; + + oldz += steptime * 80; + if (oldz > ent->origin[2]) + oldz = ent->origin[2]; + if (ent->origin[2] - oldz > 12) + oldz = ent->origin[2] - 12; + r_refdef.vieworg[2] += oldz - ent->origin[2]; + view->origin[2] += oldz - ent->origin[2]; + } + else + oldz = ent->origin[2]; + + if (chase_active.value) + Chase_UpdateForDrawing (); //johnfitz +} + +/* +================== +V_RestoreAngles + +Resets the viewentity angles to the last values received from the server +(undoing the manual adjustments performed by V_CalcRefdef) +================== +*/ +void V_RestoreAngles (void) +{ + entity_t *ent = &cl_entities[cl.viewentity]; + VectorCopy (ent->msg_angles[0], ent->angles); +} + +/* +================== +V_RenderView + +The player's clipping box goes from (-16 -16 -24) to (16 16 32) from +the entity origin, so any view position inside that will be valid +================== +*/ +extern vrect_t scr_vrect; + +void V_RenderView (void) +{ + if (con_forcedup) + return; + + if (cl.intermission) + V_CalcIntermissionRefdef (); + else if (!cl.paused /* && (cl.maxclients > 1 || key_dest == key_game) */) + V_CalcRefdef (); + + //johnfitz -- removed lcd code + + R_RenderView (); + + V_PolyBlend (); //johnfitz -- moved here from R_Renderview (); +} + +/* +============================================================================== + + INIT + +============================================================================== +*/ + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + Cmd_AddCommand ("v_cshift", V_cshift_f); + Cmd_AddCommand ("bf", V_BonusFlash_f); + Cmd_AddCommand ("centerview", V_StartPitchDrift); + + Cvar_RegisterVariable (&v_centermove); + Cvar_RegisterVariable (&v_centerspeed); + + Cvar_RegisterVariable (&v_iyaw_cycle); + Cvar_RegisterVariable (&v_iroll_cycle); + Cvar_RegisterVariable (&v_ipitch_cycle); + Cvar_RegisterVariable (&v_iyaw_level); + Cvar_RegisterVariable (&v_iroll_level); + Cvar_RegisterVariable (&v_ipitch_level); + + Cvar_RegisterVariable (&v_idlescale); + Cvar_RegisterVariable (&crosshair); + Cvar_RegisterVariable (&gl_cshiftpercent); + Cvar_RegisterVariable (&gl_cshiftpercent_contents); // QuakeSpasm + Cvar_RegisterVariable (&gl_cshiftpercent_damage); // QuakeSpasm + Cvar_RegisterVariable (&gl_cshiftpercent_bonus); // QuakeSpasm + Cvar_RegisterVariable (&gl_cshiftpercent_powerup); // QuakeSpasm + + Cvar_RegisterVariable (&scr_ofsx); + Cvar_RegisterVariable (&scr_ofsy); + Cvar_RegisterVariable (&scr_ofsz); + Cvar_RegisterVariable (&cl_rollspeed); + Cvar_RegisterVariable (&cl_rollangle); + Cvar_RegisterVariable (&cl_bob); + Cvar_RegisterVariable (&cl_bobcycle); + Cvar_RegisterVariable (&cl_bobup); + + Cvar_RegisterVariable (&v_kicktime); + Cvar_RegisterVariable (&v_kickroll); + Cvar_RegisterVariable (&v_kickpitch); + Cvar_RegisterVariable (&v_gunkick); //johnfitz + + Cvar_RegisterVariable (&r_viewmodel_quake); //MarkV +} + diff --git a/Quake/view.hpp b/Quake/view.hpp new file mode 100644 index 0000000..32d683b --- /dev/null +++ b/Quake/view.hpp @@ -0,0 +1,40 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_VIEW_H +#define _QUAKE_VIEW_H + +extern cvar_t vid_gamma; +extern cvar_t vid_contrast; + +extern float v_blend[4]; + +void V_Init (void); +void V_RenderView (void); +void V_CalcBlend (void); +void V_UpdateBlend (void); +float V_CalcRoll (vec3_t angles, vec3_t velocity); +void V_RestoreAngles (void); +//void V_UpdatePalette (void); //johnfitz + +#endif /* _QUAKE_VIEW_H */ + diff --git a/Quake/wad.cpp b/Quake/wad.cpp new file mode 100644 index 0000000..c9d1091 --- /dev/null +++ b/Quake/wad.cpp @@ -0,0 +1,167 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// wad.c + +#include "quakedef.hpp" + +int wad_numlumps; +lumpinfo_t *wad_lumps; +byte *wad_base = NULL; + +void SwapPic (qpic_t *pic); + +/* +================== +W_CleanupName + +Lowercases name and pads with spaces and a terminating 0 to the length of +lumpinfo_t->name. +Used so lumpname lookups can proceed rapidly by comparing 4 chars at a time +Space padding is so names can be printed nicely in tables. +Can safely be performed in place. +================== +*/ +void W_CleanupName (const char *in, char *out) +{ + int i; + int c; + + for (i=0 ; i<16 ; i++ ) + { + c = in[i]; + if (!c) + break; + + if (c >= 'A' && c <= 'Z') + c += ('a' - 'A'); + out[i] = c; + } + + for ( ; i< 16 ; i++ ) + out[i] = 0; +} + +/* +==================== +W_LoadWadFile +==================== +*/ +void W_LoadWadFile (void) //johnfitz -- filename is now hard-coded for honesty +{ + lumpinfo_t *lump_p; + wadinfo_t *header; + int i; + int infotableofs; + const char *filename = WADFILENAME; + + //johnfitz -- modified to use malloc + //TODO: use cache_alloc + if (wad_base) + free (wad_base); + wad_base = COM_LoadMallocFile (filename, NULL); + if (!wad_base) + Sys_Error ("W_LoadWadFile: couldn't load %s\n\n" + "Basedir is: %s\n\n" + "Check that this has an " GAMENAME " subdirectory containing pak0.pak and pak1.pak, " + "or use the -basedir command-line option to specify another directory.", + filename, com_basedir); + + header = (wadinfo_t *)wad_base; + + if (header->identification[0] != 'W' || header->identification[1] != 'A' + || header->identification[2] != 'D' || header->identification[3] != '2') + Sys_Error ("Wad file %s doesn't have WAD2 id\n",filename); + + wad_numlumps = LittleLong(header->numlumps); + infotableofs = LittleLong(header->infotableofs); + wad_lumps = (lumpinfo_t *)(wad_base + infotableofs); + + for (i=0, lump_p = wad_lumps ; ifilepos = LittleLong(lump_p->filepos); + lump_p->size = LittleLong(lump_p->size); + W_CleanupName (lump_p->name, lump_p->name); // CAUTION: in-place editing!!! + if (lump_p->type == TYP_QPIC) + SwapPic ( (qpic_t *)(wad_base + lump_p->filepos)); + } +} + + +/* +============= +W_GetLumpinfo +============= +*/ +lumpinfo_t *W_GetLumpinfo (const char *name) +{ + int i; + lumpinfo_t *lump_p; + char clean[16]; + + W_CleanupName (name, clean); + + for (lump_p=wad_lumps, i=0 ; iname)) + return lump_p; + } + + Con_SafePrintf ("W_GetLumpinfo: %s not found\n", name); //johnfitz -- was Sys_Error + return NULL; +} + +void *W_GetLumpName (const char *name) +{ + lumpinfo_t *lump; + + lump = W_GetLumpinfo (name); + + if (!lump) return NULL; //johnfitz + + return (void *)(wad_base + lump->filepos); +} + +void *W_GetLumpNum (int num) +{ + lumpinfo_t *lump; + + if (num < 0 || num > wad_numlumps) + Sys_Error ("W_GetLumpNum: bad number: %i", num); + + lump = wad_lumps + num; + + return (void *)(wad_base + lump->filepos); +} + +/* +============================================================================= + +automatic byte swapping + +============================================================================= +*/ + +void SwapPic (qpic_t *pic) +{ + pic->width = LittleLong(pic->width); + pic->height = LittleLong(pic->height); +} diff --git a/Quake/wad.hpp b/Quake/wad.hpp new file mode 100644 index 0000000..8838e0e --- /dev/null +++ b/Quake/wad.hpp @@ -0,0 +1,82 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_WAD_H +#define _QUAKE_WAD_H + +//=============== +// TYPES +//=============== + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 + +#define TYP_LUMPY 64 // 64 + grab command number +#define TYP_PALETTE 64 +#define TYP_QTEX 65 +#define TYP_QPIC 66 +#define TYP_SOUND 67 +#define TYP_MIPTEX 68 + +#define WADFILENAME "gfx.wad" //johnfitz -- filename is now hard-coded for honesty + +typedef struct +{ + int width, height; + byte data[4]; // variably sized +} qpic_t; + +typedef struct +{ + char identification[4]; // should be WAD2 or 2DAW + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; + +extern int wad_numlumps; +extern lumpinfo_t *wad_lumps; +extern byte *wad_base; + +void W_LoadWadFile (void); //johnfitz -- filename is now hard-coded for honesty +void W_CleanupName (const char *in, char *out); +lumpinfo_t *W_GetLumpinfo (const char *name); +void *W_GetLumpName (const char *name); +void *W_GetLumpNum (int num); + +void SwapPic (qpic_t *pic); + +#endif /* _QUAKE_WAD_H */ + diff --git a/Quake/world.cpp b/Quake/world.cpp new file mode 100644 index 0000000..8516a67 --- /dev/null +++ b/Quake/world.cpp @@ -0,0 +1,937 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2007-2008 Kristian Duske +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// world.c -- world query functions + +#include "quakedef.hpp" + +/* + +entities never clip against themselves, or their owner + +line of sight checks trace->crosscontent, but bullets don't + +*/ + + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; +} moveclip_t; + + +int SV_HullPointContents (hull_t *hull, int num, vec3_t p); + +/* +=============================================================================== + +HULL BOXES + +=============================================================================== +*/ + + +static hull_t box_hull; +static mclipnode_t box_clipnodes[6]; //johnfitz -- was dclipnode_t +static mplane_t box_planes[6]; + +/* +=================== +SV_InitBoxHull + +Set up the planes and clipnodes so that the six floats of a bounding box +can just be stored out and get a proper hull_t structure. +=================== +*/ +void SV_InitBoxHull (void) +{ + int i; + int side; + + box_hull.clipnodes = box_clipnodes; + box_hull.planes = box_planes; + box_hull.firstclipnode = 0; + box_hull.lastclipnode = 5; + + for (i=0 ; i<6 ; i++) + { + box_clipnodes[i].planenum = i; + + side = i&1; + + box_clipnodes[i].children[side] = CONTENTS_EMPTY; + if (i != 5) + box_clipnodes[i].children[side^1] = i + 1; + else + box_clipnodes[i].children[side^1] = CONTENTS_SOLID; + + box_planes[i].type = i>>1; + box_planes[i].normal[i>>1] = 1; + } + +} + + +/* +=================== +SV_HullForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +hull_t *SV_HullForBox (vec3_t mins, vec3_t maxs) +{ + box_planes[0].dist = maxs[0]; + box_planes[1].dist = mins[0]; + box_planes[2].dist = maxs[1]; + box_planes[3].dist = mins[1]; + box_planes[4].dist = maxs[2]; + box_planes[5].dist = mins[2]; + + return &box_hull; +} + + + +/* +================ +SV_HullForEntity + +Returns a hull that can be used for testing or clipping an object of mins/maxs +size. +Offset is filled in to contain the adjustment that must be added to the +testing object's origin to get a point to use with the returned hull. +================ +*/ +hull_t *SV_HullForEntity (edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset) +{ + qmodel_t *model; + vec3_t size; + vec3_t hullmins, hullmaxs; + hull_t *hull; + +// decide which clipping hull to use, based on the size + if (ent->v.solid == SOLID_BSP) + { // explicit hulls in the BSP model + if (ent->v.movetype != MOVETYPE_PUSH) + Host_Error ("SOLID_BSP without MOVETYPE_PUSH (%s at %f %f %f)", + PR_GetString(ent->v.classname), ent->v.origin[0], ent->v.origin[1], ent->v.origin[2]); + + model = sv.models[ (int)ent->v.modelindex ]; + + if (!model || model->type != mod_brush) + Host_Error ("SOLID_BSP with a non bsp model (%s at %f %f %f)", + PR_GetString(ent->v.classname), ent->v.origin[0], ent->v.origin[1], ent->v.origin[2]); + + VectorSubtract (maxs, mins, size); + if (size[0] < 3) + hull = &model->hulls[0]; + else if (size[0] <= 32) + hull = &model->hulls[1]; + else + hull = &model->hulls[2]; + +// calculate an offset value to center the origin + VectorSubtract (hull->clip_mins, mins, offset); + VectorAdd (offset, ent->v.origin, offset); + } + else + { // create a temp hull from bounding box sizes + + VectorSubtract (ent->v.mins, maxs, hullmins); + VectorSubtract (ent->v.maxs, mins, hullmaxs); + hull = SV_HullForBox (hullmins, hullmaxs); + + VectorCopy (ent->v.origin, offset); + } + + + return hull; +} + +/* +=============================================================================== + +ENTITY AREA CHECKING + +=============================================================================== +*/ + +typedef struct areanode_s +{ + int axis; // -1 = leaf node + float dist; + struct areanode_s *children[2]; + link_t trigger_edicts; + link_t solid_edicts; +} areanode_t; + +#define AREA_DEPTH 4 +#define AREA_NODES 32 + +static areanode_t sv_areanodes[AREA_NODES]; +static int sv_numareanodes; + +/* +=============== +SV_CreateAreaNode + +=============== +*/ +areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs) +{ + areanode_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_areanodes[sv_numareanodes]; + sv_numareanodes++; + + ClearLink (&anode->trigger_edicts); + ClearLink (&anode->solid_edicts); + + 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_CreateAreaNode (depth+1, mins2, maxs2); + anode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld (void) +{ + SV_InitBoxHull (); + + memset (sv_areanodes, 0, sizeof(sv_areanodes)); + sv_numareanodes = 0; + SV_CreateAreaNode (0, sv.worldmodel->mins, sv.worldmodel->maxs); +} + + +/* +=============== +SV_UnlinkEdict + +=============== +*/ +void SV_UnlinkEdict (edict_t *ent) +{ + if (!ent->area.prev) + return; // not linked in anywhere + RemoveLink (&ent->area); + ent->area.prev = ent->area.next = NULL; +} + + +/* +==================== +SV_AreaTriggerEdicts + +Spike -- just builds a list of entities within the area, rather than walking +them and risking the list getting corrupt. +==================== +*/ +static void +SV_AreaTriggerEdicts ( edict_t *ent, areanode_t *node, edict_t **list, int *listcount, const int listspace ) +{ + link_t *l, *next; + edict_t *touch; + +// touch linked edicts + for (l = node->trigger_edicts.next ; l != &node->trigger_edicts ; l = next) + { + next = l->next; + touch = EDICT_FROM_AREA(l); + if (touch == ent) + continue; + if (!touch->v.touch || touch->v.solid != SOLID_TRIGGER) + continue; + if (ent->v.absmin[0] > touch->v.absmax[0] + || ent->v.absmin[1] > touch->v.absmax[1] + || ent->v.absmin[2] > touch->v.absmax[2] + || ent->v.absmax[0] < touch->v.absmin[0] + || ent->v.absmax[1] < touch->v.absmin[1] + || ent->v.absmax[2] < touch->v.absmin[2] ) + continue; + + if (*listcount == listspace) + return; // should never happen + + list[*listcount] = touch; + (*listcount)++; + } + +// recurse down both sides + if (node->axis == -1) + return; + + if ( ent->v.absmax[node->axis] > node->dist ) + SV_AreaTriggerEdicts ( ent, node->children[0], list, listcount, listspace ); + if ( ent->v.absmin[node->axis] < node->dist ) + SV_AreaTriggerEdicts ( ent, node->children[1], list, listcount, listspace ); +} + +/* +==================== +SV_TouchLinks + +ericw -- copy the touching edicts to an array (on the hunk) so we can avoid +iteating the trigger_edicts linked list while calling PR_ExecuteProgram +which could potentially corrupt the list while it's being iterated. +Based on code from Spike. +==================== +*/ +void SV_TouchLinks (edict_t *ent) +{ + edict_t **list; + edict_t *touch; + int old_self, old_other; + int i, listcount; + int mark; + + mark = Hunk_LowMark (); + list = (edict_t **) Hunk_Alloc (sv.num_edicts*sizeof(edict_t *)); + + listcount = 0; + SV_AreaTriggerEdicts (ent, sv_areanodes, list, &listcount, sv.num_edicts); + + for (i = 0; i < listcount; i++) + { + touch = list[i]; + // re-validate in case of PR_ExecuteProgram having side effects that make + // edicts later in the list no longer touch + if (touch == ent) + continue; + if (!touch->v.touch || touch->v.solid != SOLID_TRIGGER) + continue; + if (ent->v.absmin[0] > touch->v.absmax[0] + || ent->v.absmin[1] > touch->v.absmax[1] + || ent->v.absmin[2] > touch->v.absmax[2] + || ent->v.absmax[0] < touch->v.absmin[0] + || ent->v.absmax[1] < touch->v.absmin[1] + || ent->v.absmax[2] < touch->v.absmin[2] ) + continue; + old_self = pr_global_struct->self; + old_other = pr_global_struct->other; + + pr_global_struct->self = EDICT_TO_PROG(touch); + pr_global_struct->other = EDICT_TO_PROG(ent); + pr_global_struct->time = sv.time; + PR_ExecuteProgram (touch->v.touch); + + pr_global_struct->self = old_self; + pr_global_struct->other = old_other; + } + +// free hunk-allocated edicts array + Hunk_FreeToLowMark (mark); +} + + +/* +=============== +SV_FindTouchedLeafs + +=============== +*/ +void SV_FindTouchedLeafs (edict_t *ent, mnode_t *node) +{ + mplane_t *splitplane; + mleaf_t *leaf; + int sides; + int leafnum; + + if (node->contents == CONTENTS_SOLID) + return; + + if (ent->num_leafs == MAX_ENT_LEAFS) + return; + +// add an efrag if the node is a leaf + + if ( node->contents < 0) + { + leaf = (mleaf_t *)node; + leafnum = leaf - sv.worldmodel->leafs - 1; + + ent->leafnums[ent->num_leafs] = leafnum; + ent->num_leafs++; + return; + } + +// NODE_MIXED + + splitplane = node->plane; + sides = BOX_ON_PLANE_SIDE(ent->v.absmin, ent->v.absmax, splitplane); + +// recurse down the contacted sides + if (sides & 1) + SV_FindTouchedLeafs (ent, node->children[0]); + + if (sides & 2) + SV_FindTouchedLeafs (ent, node->children[1]); +} + +/* +=============== +SV_LinkEdict + +=============== +*/ +void SV_LinkEdict (edict_t *ent, qboolean touch_triggers) +{ + areanode_t *node; + + if (ent->area.prev) + SV_UnlinkEdict (ent); // unlink from old position + + if (ent == sv.edicts) + return; // don't add the world + + if (ent->free) + return; + +// set the abs box + VectorAdd (ent->v.origin, ent->v.mins, ent->v.absmin); + VectorAdd (ent->v.origin, ent->v.maxs, ent->v.absmax); + +// +// to make items easier to pick up and allow them to be grabbed off +// of shelves, the abs sizes are expanded +// + if ((int)ent->v.flags & FL_ITEM) + { + ent->v.absmin[0] -= 15; + ent->v.absmin[1] -= 15; + ent->v.absmax[0] += 15; + ent->v.absmax[1] += 15; + } + else + { // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + ent->v.absmin[0] -= 1; + ent->v.absmin[1] -= 1; + ent->v.absmin[2] -= 1; + ent->v.absmax[0] += 1; + ent->v.absmax[1] += 1; + ent->v.absmax[2] += 1; + } + +// link to PVS leafs + ent->num_leafs = 0; + if (ent->v.modelindex) + SV_FindTouchedLeafs (ent, sv.worldmodel->nodes); + + if (ent->v.solid == SOLID_NOT) + return; + +// find the first node that the ent's box crosses + node = sv_areanodes; + while (1) + { + if (node->axis == -1) + break; + if (ent->v.absmin[node->axis] > node->dist) + node = node->children[0]; + else if (ent->v.absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + +// link it in + + if (ent->v.solid == SOLID_TRIGGER) + InsertLinkBefore (&ent->area, &node->trigger_edicts); + else + InsertLinkBefore (&ent->area, &node->solid_edicts); + +// if touch_triggers, touch all entities at this node and decend for more + if (touch_triggers) + SV_TouchLinks ( ent ); +} + + + +/* +=============================================================================== + +POINT TESTING IN HULLS + +=============================================================================== +*/ + +/* +================== +SV_HullPointContents + +================== +*/ +int SV_HullPointContents (hull_t *hull, int num, vec3_t p) +{ + float d; + mclipnode_t *node; //johnfitz -- was dclipnode_t + mplane_t *plane; + + while (num >= 0) + { + if (num < hull->firstclipnode || num > hull->lastclipnode) + Sys_Error ("SV_HullPointContents: bad node number"); + + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DoublePrecisionDotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + return num; +} + + +/* +================== +SV_PointContents + +================== +*/ +int SV_PointContents (vec3_t p) +{ + int cont; + + cont = SV_HullPointContents (&sv.worldmodel->hulls[0], 0, p); + if (cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN) + cont = CONTENTS_WATER; + return cont; +} + +int SV_TruePointContents (vec3_t p) +{ + return SV_HullPointContents (&sv.worldmodel->hulls[0], 0, p); +} + +//=========================================================================== + +/* +============ +SV_TestEntityPosition + +This could be a lot more efficient... +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + + trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, 0, ent); + + if (trace.startsolid) + return sv.edicts; + + return NULL; +} + + +/* +=============================================================================== + +LINE TESTING IN HULLS + +=============================================================================== +*/ + +/* +================== +SV_RecursiveHullCheck + +================== +*/ +qboolean SV_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace) +{ + mclipnode_t *node; //johnfitz -- was dclipnode_t + mplane_t *plane; + float t1, t2; + float frac; + int i; + vec3_t mid; + int side; + float midf; + +// check for empty + if (num < 0) + { + if (num != CONTENTS_SOLID) + { + trace->allsolid = false; + if (num == CONTENTS_EMPTY) + trace->inopen = true; + else + trace->inwater = true; + } + else + trace->startsolid = true; + return true; // empty + } + + if (num < hull->firstclipnode || num > hull->lastclipnode) + Sys_Error ("SV_RecursiveHullCheck: bad node number"); + +// +// find the point distances +// + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + if (plane->type < 3) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DoublePrecisionDotProduct (plane->normal, p1) - plane->dist; + t2 = DoublePrecisionDotProduct (plane->normal, p2) - plane->dist; + } + +#if 1 + if (t1 >= 0 && t2 >= 0) + return SV_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace); + if (t1 < 0 && t2 < 0) + return SV_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace); +#else + if ( (t1 >= DIST_EPSILON && t2 >= DIST_EPSILON) || (t2 > t1 && t1 >= 0) ) + return SV_RecursiveHullCheck (hull, node->children[0], p1f, p2f, p1, p2, trace); + if ( (t1 <= -DIST_EPSILON && t2 <= -DIST_EPSILON) || (t2 < t1 && t1 <= 0) ) + return SV_RecursiveHullCheck (hull, node->children[1], p1f, p2f, p1, p2, trace); +#endif + +// put the crosspoint DIST_EPSILON pixels on the near side + if (t1 < 0) + frac = (t1 + DIST_EPSILON)/(t1-t2); + else + frac = (t1 - DIST_EPSILON)/(t1-t2); + if (frac < 0) + frac = 0; + if (frac > 1) + frac = 1; + + midf = p1f + (p2f - p1f)*frac; + for (i=0 ; i<3 ; i++) + mid[i] = p1[i] + frac*(p2[i] - p1[i]); + + side = (t1 < 0); + +// move up to the node + if (!SV_RecursiveHullCheck (hull, node->children[side], p1f, midf, p1, mid, trace) ) + return false; + +#ifdef PARANOID + if (SV_HullPointContents (sv_hullmodel, mid, node->children[side]) + == CONTENTS_SOLID) + { + Con_Printf ("mid PointInHullSolid\n"); + return false; + } +#endif + + if (SV_HullPointContents (hull, node->children[side^1], mid) + != CONTENTS_SOLID) +// go past the node + return SV_RecursiveHullCheck (hull, node->children[side^1], midf, p2f, mid, p2, trace); + + if (trace->allsolid) + return false; // never got out of the solid area + +//================== +// the other side of the node is solid, this is the impact point +//================== + if (!side) + { + VectorCopy (plane->normal, trace->plane.normal); + trace->plane.dist = plane->dist; + } + else + { + VectorSubtract (vec3_origin, plane->normal, trace->plane.normal); + trace->plane.dist = -plane->dist; + } + + while (SV_HullPointContents (hull, hull->firstclipnode, mid) + == CONTENTS_SOLID) + { // shouldn't really happen, but does occasionally + frac -= 0.1; + if (frac < 0) + { + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + Con_DPrintf ("backup past 0\n"); + return false; + } + midf = p1f + (p2f - p1f)*frac; + for (i=0 ; i<3 ; i++) + mid[i] = p1[i] + frac*(p2[i] - p1[i]); + } + + trace->fraction = midf; + VectorCopy (mid, trace->endpos); + + return false; +} + + +/* +================== +SV_ClipMoveToEntity + +Handles selection or creation of a clipping hull, and offseting (and +eventually rotation) of the end points +================== +*/ +trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + trace_t trace; + vec3_t offset; + vec3_t start_l, end_l; + hull_t *hull; + +// fill in a default trace + memset (&trace, 0, sizeof(trace_t)); + trace.fraction = 1; + trace.allsolid = true; + VectorCopy (end, trace.endpos); + +// get the clipping hull + hull = SV_HullForEntity (ent, mins, maxs, offset); + + VectorSubtract (start, offset, start_l); + VectorSubtract (end, offset, end_l); + +// trace a line through the apropriate clipping hull + SV_RecursiveHullCheck (hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace); + +// fix trace up by the offset + if (trace.fraction != 1) + VectorAdd (trace.endpos, offset, trace.endpos); + +// did we clip the move? + if (trace.fraction < 1 || trace.startsolid ) + trace.ent = ent; + + return trace; +} + +//=========================================================================== + +/* +==================== +SV_ClipToLinks + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +void SV_ClipToLinks ( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + edict_t *touch; + trace_t trace; + +// touch linked edicts + for (l = node->solid_edicts.next ; l != &node->solid_edicts ; l = next) + { + next = l->next; + touch = EDICT_FROM_AREA(l); + if (touch->v.solid == SOLID_NOT) + continue; + if (touch == clip->passedict) + continue; + if (touch->v.solid == SOLID_TRIGGER) + Sys_Error ("Trigger in clipping list"); + + if (clip->type == MOVE_NOMONSTERS && touch->v.solid != SOLID_BSP) + continue; + + if (clip->boxmins[0] > touch->v.absmax[0] + || clip->boxmins[1] > touch->v.absmax[1] + || clip->boxmins[2] > touch->v.absmax[2] + || clip->boxmaxs[0] < touch->v.absmin[0] + || clip->boxmaxs[1] < touch->v.absmin[1] + || clip->boxmaxs[2] < touch->v.absmin[2] ) + continue; + + if (clip->passedict && clip->passedict->v.size[0] && !touch->v.size[0]) + continue; // points never interact + + // might intersect, so do an exact clip + if (clip->trace.allsolid) + return; + if (clip->passedict) + { + if (PROG_TO_EDICT(touch->v.owner) == clip->passedict) + continue; // don't clip against own missiles + if (PROG_TO_EDICT(clip->passedict->v.owner) == touch) + continue; // don't clip against owner + } + + if ((int)touch->v.flags & FL_MONSTER) + trace = SV_ClipMoveToEntity (touch, clip->start, clip->mins2, clip->maxs2, clip->end); + else + trace = SV_ClipMoveToEntity (touch, clip->start, clip->mins, clip->maxs, clip->end); + if (trace.allsolid || trace.startsolid || + trace.fraction < clip->trace.fraction) + { + trace.ent = touch; + if (clip->trace.startsolid) + { + clip->trace = trace; + clip->trace.startsolid = true; + } + else + clip->trace = trace; + } + else if (trace.startsolid) + clip->trace.startsolid = true; + } + +// recurse down both sides + if (node->axis == -1) + return; + + if ( clip->boxmaxs[node->axis] > node->dist ) + SV_ClipToLinks ( node->children[0], clip ); + if ( clip->boxmins[node->axis] < node->dist ) + SV_ClipToLinks ( node->children[1], clip ); +} + + +/* +================== +SV_MoveBounds +================== +*/ +void SV_MoveBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs) +{ +#if 0 +// debug to test against everything +boxmins[0] = boxmins[1] = boxmins[2] = -9999; +boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999; +#else + int i; + + for (i=0 ; i<3 ; i++) + { + if (end[i] > start[i]) + { + boxmins[i] = start[i] + mins[i] - 1; + boxmaxs[i] = end[i] + maxs[i] + 1; + } + else + { + boxmins[i] = end[i] + mins[i] - 1; + boxmaxs[i] = start[i] + maxs[i] + 1; + } + } +#endif +} + +/* +================== +SV_Move +================== +*/ +trace_t SV_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict) +{ + moveclip_t clip; + int i; + + memset ( &clip, 0, sizeof ( moveclip_t ) ); + +// clip to world + clip.trace = SV_ClipMoveToEntity ( sv.edicts, start, mins, maxs, end ); + + clip.start = start; + clip.end = end; + clip.mins = mins; + clip.maxs = maxs; + clip.type = type; + clip.passedict = passedict; + + if (type == MOVE_MISSILE) + { + for (i=0 ; i<3 ; i++) + { + clip.mins2[i] = -15; + clip.maxs2[i] = 15; + } + } + else + { + VectorCopy (mins, clip.mins2); + VectorCopy (maxs, clip.maxs2); + } + +// create the bounding box of the entire move + SV_MoveBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs ); + +// clip to entities + SV_ClipToLinks ( sv_areanodes, &clip ); + + return clip.trace; +} + diff --git a/Quake/world.hpp b/Quake/world.hpp new file mode 100644 index 0000000..fed90b0 --- /dev/null +++ b/Quake/world.hpp @@ -0,0 +1,87 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _QUAKE_WORLD_H +#define _QUAKE_WORLD_H + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on +} trace_t; + + +#define MOVE_NORMAL 0 +#define MOVE_NOMONSTERS 1 +#define MOVE_MISSILE 2 + + +void SV_ClearWorld (void); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEdict (edict_t *ent); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself +// flags ent->v.modified + +void SV_LinkEdict (edict_t *ent, qboolean touch_triggers); +// Needs to be called any time an entity changes origin, mins, maxs, or solid +// flags ent->v.modified +// sets ent->v.absmin and ent->v.absmax +// if touchtriggers, calls prog functions for the intersected triggers + +int SV_PointContents (vec3_t p); +int SV_TruePointContents (vec3_t p); +// returns the CONTENTS_* value from the world at the given point. +// does not check any entities at all +// the non-true version remaps the water current contents to content_water + +edict_t *SV_TestEntityPosition (edict_t *ent); + +trace_t SV_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, edict_t *passedict); +// mins and maxs are reletive + +// if the entire move stays in a solid volume, trace.allsolid will be set + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// nomonsters is used for line of sight or edge testing, where mosnters +// shouldn't be considered solid objects + +// passedict is explicitly excluded from clipping checks (normally NULL) + +qboolean SV_RecursiveHullCheck (hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, trace_t *trace); + +#endif /* _QUAKE_WORLD_H */ + diff --git a/Quake/wsaerror.hpp b/Quake/wsaerror.hpp new file mode 100644 index 0000000..b41a98e --- /dev/null +++ b/Quake/wsaerror.hpp @@ -0,0 +1,91 @@ + +/* strings for winsock error codes. + * from online references, such as + * http://aluigi.org/mytoolz/winerr.h + * http://www.winsock-error.com or + * http://www.sockets.com/err_lst1.htm + */ + +#ifndef __wsaerr_static +#define __wsaerr_static static +#endif /* static */ + +__wsaerr_static const char *__WSAE_StrError (int err) +{ + switch (err) + { + case 0: return "No error"; + case WSAEINTR: return "Interrupted system call"; /* 10004 */ + case WSAEBADF: return "Bad file number"; /* 10009 */ + case WSAEACCES: return "Permission denied"; /* 10013 */ + case WSAEFAULT: return "Bad address"; /* 10014 */ + case WSAEINVAL: return "Invalid argument (not bind)"; /* 10022 */ + case WSAEMFILE: return "Too many open files"; /* 10024 */ + case WSAEWOULDBLOCK: return "Operation would block"; /* 10035 */ + case WSAEINPROGRESS: return "Operation now in progress"; /* 10036 */ + case WSAEALREADY: return "Operation already in progress"; /* 10037 */ + case WSAENOTSOCK: return "Socket operation on non-socket"; /* 10038 */ + case WSAEDESTADDRREQ: return "Destination address required"; /* 10039 */ + case WSAEMSGSIZE: return "Message too long"; /* 10040 */ + case WSAEPROTOTYPE: return "Protocol wrong type for socket"; /* 10041 */ + case WSAENOPROTOOPT: return "Bad protocol option"; /* 10042 */ + case WSAEPROTONOSUPPORT: return "Protocol not supported"; /* 10043 */ + case WSAESOCKTNOSUPPORT: return "Socket type not supported"; /* 10044 */ + case WSAEOPNOTSUPP: return "Operation not supported on socket"; /* 10045 */ + case WSAEPFNOSUPPORT: return "Protocol family not supported"; /* 10046 */ + case WSAEAFNOSUPPORT: return "Address family not supported by protocol family"; /* 10047 */ + case WSAEADDRINUSE: return "Address already in use"; /* 10048 */ + case WSAEADDRNOTAVAIL: return "Can't assign requested address"; /* 10049 */ + case WSAENETDOWN: return "Network is down"; /* 10050 */ + case WSAENETUNREACH: return "Network is unreachable"; /* 10051 */ + case WSAENETRESET: return "Net dropped connection or reset"; /* 10052 */ + case WSAECONNABORTED: return "Software caused connection abort"; /* 10053 */ + case WSAECONNRESET: return "Connection reset by peer"; /* 10054 */ + case WSAENOBUFS: return "No buffer space available"; /* 10055 */ + case WSAEISCONN: return "Socket is already connected"; /* 10056 */ + case WSAENOTCONN: return "Socket is not connected"; /* 10057 */ + case WSAESHUTDOWN: return "Can't send after socket shutdown"; /* 10058 */ + case WSAETOOMANYREFS: return "Too many references, can't splice"; /* 10059 */ + case WSAETIMEDOUT: return "Connection timed out"; /* 10060 */ + case WSAECONNREFUSED: return "Connection refused"; /* 10061 */ + case WSAELOOP: return "Too many levels of symbolic links"; /* 10062 */ + case WSAENAMETOOLONG: return "File name too long"; /* 10063 */ + case WSAEHOSTDOWN: return "Host is down"; /* 10064 */ + case WSAEHOSTUNREACH: return "No Route to Host"; /* 10065 */ + case WSAENOTEMPTY: return "Directory not empty"; /* 10066 */ + case WSAEPROCLIM: return "Too many processes"; /* 10067 */ + case WSAEUSERS: return "Too many users"; /* 10068 */ + case WSAEDQUOT: return "Disc Quota Exceeded"; /* 10069 */ + case WSAESTALE: return "Stale NFS file handle"; /* 10070 */ + case WSAEREMOTE: return "Too many levels of remote in path"; /* 10071 */ + case WSAEDISCON: return "Graceful shutdown in progress"; /* 10101 */ + + case WSASYSNOTREADY: return "Network SubSystem is unavailable"; /* 10091 */ + case WSAVERNOTSUPPORTED: return "WINSOCK DLL Version out of range"; /* 10092 */ + case WSANOTINITIALISED: return "Successful WSASTARTUP not yet performed"; /* 10093 */ + case WSAHOST_NOT_FOUND: return "Authoritative answer: Host not found"; /* 11001 */ + case WSATRY_AGAIN: return "Non-Authoritative: Host not found or SERVERFAIL"; /* 11002 */ + case WSANO_RECOVERY: return "Non-Recoverable errors, FORMERR, REFUSED, NOTIMP"; /* 11003 */ + case WSANO_DATA: return "Valid name, no data record of requested type"; /* 11004 */ + + case WSAENOMORE: return "10102: No more results"; /* 10102 */ + case WSAECANCELLED: return "10103: Call has been canceled"; /* 10103 */ + case WSAEINVALIDPROCTABLE: return "Procedure call table is invalid"; /* 10104 */ + case WSAEINVALIDPROVIDER: return "Service provider is invalid"; /* 10105 */ + case WSAEPROVIDERFAILEDINIT: return "Service provider failed to initialize"; /* 10106 */ + case WSASYSCALLFAILURE: return "System call failure"; /* 10107 */ + case WSASERVICE_NOT_FOUND: return "Service not found"; /* 10108 */ + case WSATYPE_NOT_FOUND: return "Class type not found"; /* 10109 */ + case WSA_E_NO_MORE: return "10110: No more results"; /* 10110 */ + case WSA_E_CANCELLED: return "10111: Call was canceled"; /* 10111 */ + case WSAEREFUSED: return "Database query was refused"; /* 10112 */ + + default: + { + static char _err_unknown[64]; + sprintf(_err_unknown, "Unknown WSAE error (%d)", err); + return _err_unknown; + } + } +} + diff --git a/Quake/zone.cpp b/Quake/zone.cpp new file mode 100644 index 0000000..37044ca --- /dev/null +++ b/Quake/zone.cpp @@ -0,0 +1,989 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// zone.c + +#include "quakedef.hpp" + +#define DYNAMIC_SIZE (4 * 1024 * 1024) // ericw -- was 512KB (64-bit) / 384KB (32-bit) + +#define ZONEID 0x1d4a11 +#define MINFRAGMENT 64 + +typedef struct memblock_s +{ + int size; // including the header and possibly tiny fragments + int tag; // a tag of 0 is a free block + int id; // should be ZONEID + int pad; // pad to 64 bit boundary + struct memblock_s *next, *prev; +} memblock_t; + +typedef struct +{ + int size; // total bytes malloced, including header + memblock_t blocklist; // start / end cap for linked list + memblock_t *rover; +} memzone_t; + +void Cache_FreeLow (int new_low_hunk); +void Cache_FreeHigh (int new_high_hunk); + + +/* +============================================================================== + + 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. +============================================================================== +*/ + +static memzone_t *mainzone; + + +/* +======================== +Z_Free +======================== +*/ +void Z_Free (void *ptr) +{ + memblock_t *block, *other; + + if (!ptr) + Sys_Error ("Z_Free: NULL pointer"); + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + if (block->id != ZONEID) + Sys_Error ("Z_Free: freed a pointer without ZONEID"); + if (block->tag == 0) + Sys_Error ("Z_Free: freed a freed pointer"); + + 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 == mainzone->rover) + mainzone->rover = other; + block = other; + } + + 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; + if (other == mainzone->rover) + mainzone->rover = block; + } +} + + +static void *Z_TagMalloc (int size, int tag) +{ + int extra; + memblock_t *start, *rover, *newblock, *base; + + if (!tag) + Sys_Error ("Z_TagMalloc: tried to use a 0 tag"); + +// +// 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 = (size + 7) & ~7; // align to 8-byte boundary + + base = rover = mainzone->rover; + start = base->prev; + + do + { + if (rover == start) // scaned all the way around the list + 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 + newblock = (memblock_t *) ((byte *)base + size ); + newblock->size = extra; + newblock->tag = 0; // free block + newblock->prev = base; + newblock->id = ZONEID; + newblock->next = base->next; + newblock->next->prev = newblock; + base->next = newblock; + base->size = size; + } + + base->tag = tag; // no longer a free block + + mainzone->rover = base->next; // next allocation will start looking here + + base->id = ZONEID; + +// marker for memory trash testing + *(int *)((byte *)base + base->size - 4) = ZONEID; + + return (void *) ((byte *)base + sizeof(memblock_t)); +} + +/* +======================== +Z_CheckHeap +======================== +*/ +static 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) + Sys_Error ("Z_CheckHeap: block size does not touch the next block"); + if ( block->next->prev != block) + Sys_Error ("Z_CheckHeap: next block doesn't have proper back link"); + if (!block->tag && !block->next->tag) + Sys_Error ("Z_CheckHeap: two consecutive free blocks"); + } +} + + +/* +======================== +Z_Malloc +======================== +*/ +void *Z_Malloc (int size) +{ + void *buf; + + Z_CheckHeap (); // DEBUG + buf = Z_TagMalloc (size, 1); + if (!buf) + Sys_Error ("Z_Malloc: failed on allocation of %i bytes",size); + Q_memset (buf, 0, size); + + return buf; +} + +/* +======================== +Z_Realloc +======================== +*/ +void *Z_Realloc(void *ptr, int size) +{ + int old_size; + void *old_ptr; + memblock_t *block; + + if (!ptr) + return Z_Malloc (size); + + block = (memblock_t *) ((byte *) ptr - sizeof (memblock_t)); + if (block->id != ZONEID) + Sys_Error ("Z_Realloc: realloced a pointer without ZONEID"); + if (block->tag == 0) + Sys_Error ("Z_Realloc: realloced a freed pointer"); + + old_size = block->size; + old_size -= (4 + (int)sizeof(memblock_t)); /* see Z_TagMalloc() */ + old_ptr = ptr; + + Z_Free (ptr); + ptr = Z_TagMalloc (size, 1); + if (!ptr) + Sys_Error ("Z_Realloc: failed on allocation of %i bytes", size); + + if (ptr != old_ptr) + memmove (ptr, old_ptr, q_min(old_size, size)); + if (old_size < size) + memset ((byte *)ptr + old_size, 0, size - old_size); + + return ptr; +} + +char *Z_Strdup (const char *s) +{ + size_t sz = strlen(s) + 1; + char *ptr = (char *) Z_Malloc (sz); + memcpy (ptr, s, sz); + return ptr; +} + + +/* +======================== +Z_Print +======================== +*/ +void Z_Print (memzone_t *zone) +{ + memblock_t *block; + + Con_Printf ("zone size: %i location: %p\n",mainzone->size,mainzone); + + for (block = zone->blocklist.next ; ; block = block->next) + { + Con_Printf ("block:%p size:%7i tag:%3i\n", + block, block->size, block->tag); + + if (block->next == &zone->blocklist) + break; // all blocks have been hit + if ( (byte *)block + block->size != (byte *)block->next) + Con_Printf ("ERROR: block size does not touch the next block\n"); + if ( block->next->prev != block) + Con_Printf ("ERROR: next block doesn't have proper back link\n"); + if (!block->tag && !block->next->tag) + Con_Printf ("ERROR: two consecutive free blocks\n"); + } +} + + +//============================================================================ + +#define HUNK_SENTINEL 0x1df001ed + +#define HUNKNAME_LEN 24 +typedef struct +{ + int sentinel; + int size; // including sizeof(hunk_t), -1 = not allocated + char name[HUNKNAME_LEN]; +} hunk_t; + +byte *hunk_base; +int hunk_size; + +int hunk_low_used; +int hunk_high_used; + +qboolean hunk_tempactive; +int hunk_tempmark; + +/* +============== +Hunk_Check + +Run consistancy and sentinel trahing checks +============== +*/ +void Hunk_Check (void) +{ + hunk_t *h; + + for (h = (hunk_t *)hunk_base ; (byte *)h != hunk_base + hunk_low_used ; ) + { + if (h->sentinel != HUNK_SENTINEL) + Sys_Error ("Hunk_Check: trashed sentinel"); + if (h->size < (int) sizeof(hunk_t) || h->size + (byte *)h - hunk_base > hunk_size) + Sys_Error ("Hunk_Check: bad size"); + h = (hunk_t *)((byte *)h+h->size); + } +} + +/* +============== +Hunk_Print + +If "all" is specified, every single allocation is printed. +Otherwise, allocations with the same name will be totaled up before printing. +============== +*/ +void Hunk_Print (qboolean all) +{ + hunk_t *h, *next, *endlow, *starthigh, *endhigh; + int count, sum; + int totalblocks; + char name[HUNKNAME_LEN]; + + count = 0; + sum = 0; + totalblocks = 0; + + h = (hunk_t *)hunk_base; + endlow = (hunk_t *)(hunk_base + hunk_low_used); + starthigh = (hunk_t *)(hunk_base + hunk_size - hunk_high_used); + endhigh = (hunk_t *)(hunk_base + hunk_size); + + Con_Printf (" :%8i total hunk size\n", hunk_size); + Con_Printf ("-------------------------\n"); + + while (1) + { + // + // skip to the high hunk if done with low hunk + // + if ( h == endlow ) + { + Con_Printf ("-------------------------\n"); + Con_Printf (" :%8i REMAINING\n", hunk_size - hunk_low_used - hunk_high_used); + Con_Printf ("-------------------------\n"); + h = starthigh; + } + + // + // if totally done, break + // + if ( h == endhigh ) + break; + + // + // run consistancy checks + // + if (h->sentinel != HUNK_SENTINEL) + Sys_Error ("Hunk_Check: trashed sentinel"); + if (h->size < (int) sizeof(hunk_t) || h->size + (byte *)h - hunk_base > hunk_size) + Sys_Error ("Hunk_Check: bad size"); + + next = (hunk_t *)((byte *)h+h->size); + count++; + totalblocks++; + sum += h->size; + + // + // print the single block + // + memcpy (name, h->name, HUNKNAME_LEN); + if (all) + Con_Printf ("%8p :%8i %8s\n",h, h->size, name); + + // + // print the total + // + if (next == endlow || next == endhigh || + strncmp (h->name, next->name, HUNKNAME_LEN - 1)) + { + if (!all) + Con_Printf (" :%8i %8s (TOTAL)\n",sum, name); + count = 0; + sum = 0; + } + + h = next; + } + + Con_Printf ("-------------------------\n"); + Con_Printf ("%8i total blocks\n", totalblocks); + +} + +/* +=================== +Hunk_Print_f -- johnfitz -- console command to call hunk_print +=================== +*/ +void Hunk_Print_f (void) +{ + Hunk_Print (false); +} + +/* +=================== +Hunk_AllocName +=================== +*/ +void *Hunk_AllocName (int size, const char *name) +{ + hunk_t *h; + +#ifdef PARANOID + Hunk_Check (); +#endif + + if (size < 0) + Sys_Error ("Hunk_Alloc: bad size: %i", size); + + size = sizeof(hunk_t) + ((size+15)&~15); + + if (hunk_size - hunk_low_used - hunk_high_used < size) + Sys_Error ("Hunk_Alloc: failed on %i bytes",size); + + h = (hunk_t *)(hunk_base + hunk_low_used); + hunk_low_used += size; + + Cache_FreeLow (hunk_low_used); + + memset (h, 0, size); + + h->size = size; + h->sentinel = HUNK_SENTINEL; + q_strlcpy (h->name, name, HUNKNAME_LEN); + + return (void *)(h+1); +} + +/* +=================== +Hunk_Alloc +=================== +*/ +void *Hunk_Alloc (int size) +{ + return Hunk_AllocName (size, "unknown"); +} + +int Hunk_LowMark (void) +{ + return hunk_low_used; +} + +void Hunk_FreeToLowMark (int mark) +{ + if (mark < 0 || mark > hunk_low_used) + Sys_Error ("Hunk_FreeToLowMark: bad mark %i", mark); + memset (hunk_base + mark, 0, hunk_low_used - mark); + hunk_low_used = mark; +} + +int Hunk_HighMark (void) +{ + if (hunk_tempactive) + { + hunk_tempactive = false; + Hunk_FreeToHighMark (hunk_tempmark); + } + + return hunk_high_used; +} + +void Hunk_FreeToHighMark (int mark) +{ + if (hunk_tempactive) + { + hunk_tempactive = false; + Hunk_FreeToHighMark (hunk_tempmark); + } + if (mark < 0 || mark > hunk_high_used) + Sys_Error ("Hunk_FreeToHighMark: bad mark %i", mark); + memset (hunk_base + hunk_size - hunk_high_used, 0, hunk_high_used - mark); + hunk_high_used = mark; +} + + +/* +=================== +Hunk_HighAllocName +=================== +*/ +void *Hunk_HighAllocName (int size, const char *name) +{ + hunk_t *h; + + if (size < 0) + Sys_Error ("Hunk_HighAllocName: bad size: %i", size); + + if (hunk_tempactive) + { + Hunk_FreeToHighMark (hunk_tempmark); + hunk_tempactive = false; + } + +#ifdef PARANOID + Hunk_Check (); +#endif + + size = sizeof(hunk_t) + ((size+15)&~15); + + if (hunk_size - hunk_low_used - hunk_high_used < size) + { + Con_Printf ("Hunk_HighAlloc: failed on %i bytes\n",size); + return NULL; + } + + hunk_high_used += size; + Cache_FreeHigh (hunk_high_used); + + h = (hunk_t *)(hunk_base + hunk_size - hunk_high_used); + + memset (h, 0, size); + h->size = size; + h->sentinel = HUNK_SENTINEL; + q_strlcpy (h->name, name, HUNKNAME_LEN); + + return (void *)(h+1); +} + + +/* +================= +Hunk_TempAlloc + +Return space from the top of the hunk +================= +*/ +void *Hunk_TempAlloc (int size) +{ + void *buf; + + size = (size+15)&~15; + + if (hunk_tempactive) + { + Hunk_FreeToHighMark (hunk_tempmark); + hunk_tempactive = false; + } + + hunk_tempmark = Hunk_HighMark (); + + buf = Hunk_HighAllocName (size, "temp"); + + hunk_tempactive = true; + + return buf; +} + +char *Hunk_Strdup (const char *s, const char *name) +{ + size_t sz = strlen(s) + 1; + char *ptr = (char *) Hunk_AllocName (sz, name); + memcpy (ptr, s, sz); + return ptr; +} + +/* +=============================================================================== + +CACHE MEMORY + +=============================================================================== +*/ + +#define CACHENAME_LEN 32 +typedef struct cache_system_s +{ + int size; // including this header + cache_user_t *user; + char name[CACHENAME_LEN]; + struct cache_system_s *prev, *next; + struct cache_system_s *lru_prev, *lru_next; // for LRU flushing +} cache_system_t; + +cache_system_t *Cache_TryAlloc (int size, qboolean nobottom); + +cache_system_t cache_head; + +/* +=========== +Cache_Move +=========== +*/ +void Cache_Move ( cache_system_t *c) +{ + cache_system_t *new_cs; + +// we are clearing up space at the bottom, so only allocate it late + new_cs = Cache_TryAlloc (c->size, true); + if (new_cs) + { +// Con_Printf ("cache_move ok\n"); + + Q_memcpy ( new_cs+1, c+1, c->size - sizeof(cache_system_t) ); + new_cs->user = c->user; + Q_memcpy (new_cs->name, c->name, sizeof(new_cs->name)); + Cache_Free (c->user, false); //johnfitz -- added second argument + new_cs->user->data = (void *)(new_cs+1); + } + else + { +// Con_Printf ("cache_move failed\n"); + + Cache_Free (c->user, true); // tough luck... //johnfitz -- added second argument + } +} + +/* +============ +Cache_FreeLow + +Throw things out until the hunk can be expanded to the given point +============ +*/ +void Cache_FreeLow (int new_low_hunk) +{ + cache_system_t *c; + + while (1) + { + c = cache_head.next; + if (c == &cache_head) + return; // nothing in cache at all + if ((byte *)c >= hunk_base + new_low_hunk) + return; // there is space to grow the hunk + Cache_Move ( c ); // reclaim the space + } +} + +/* +============ +Cache_FreeHigh + +Throw things out until the hunk can be expanded to the given point +============ +*/ +void Cache_FreeHigh (int new_high_hunk) +{ + cache_system_t *c, *prev; + + prev = NULL; + while (1) + { + c = cache_head.prev; + if (c == &cache_head) + return; // nothing in cache at all + if ( (byte *)c + c->size <= hunk_base + hunk_size - new_high_hunk) + return; // there is space to grow the hunk + if (c == prev) + Cache_Free (c->user, true); // didn't move out of the way //johnfitz -- added second argument + else + { + Cache_Move (c); // try to move it + prev = c; + } + } +} + +void Cache_UnlinkLRU (cache_system_t *cs) +{ + if (!cs->lru_next || !cs->lru_prev) + Sys_Error ("Cache_UnlinkLRU: NULL link"); + + cs->lru_next->lru_prev = cs->lru_prev; + cs->lru_prev->lru_next = cs->lru_next; + + cs->lru_prev = cs->lru_next = NULL; +} + +void Cache_MakeLRU (cache_system_t *cs) +{ + if (cs->lru_next || cs->lru_prev) + Sys_Error ("Cache_MakeLRU: active link"); + + cache_head.lru_next->lru_prev = cs; + cs->lru_next = cache_head.lru_next; + cs->lru_prev = &cache_head; + cache_head.lru_next = cs; +} + +/* +============ +Cache_TryAlloc + +Looks for a free block of memory between the high and low hunk marks +Size should already include the header and padding +============ +*/ +cache_system_t *Cache_TryAlloc (int size, qboolean nobottom) +{ + cache_system_t *cs, *new_cs; + +// is the cache completely empty? + + if (!nobottom && cache_head.prev == &cache_head) + { + if (hunk_size - hunk_high_used - hunk_low_used < size) + Sys_Error ("Cache_TryAlloc: %i is greater then free hunk", size); + + new_cs = (cache_system_t *) (hunk_base + hunk_low_used); + memset (new_cs, 0, sizeof(*new_cs)); + new_cs->size = size; + + cache_head.prev = cache_head.next = new_cs; + new_cs->prev = new_cs->next = &cache_head; + + Cache_MakeLRU (new_cs); + return new_cs; + } + +// search from the bottom up for space + + new_cs = (cache_system_t *) (hunk_base + hunk_low_used); + cs = cache_head.next; + + do + { + if (!nobottom || cs != cache_head.next) + { + if ( (byte *)cs - (byte *)new_cs >= size) + { // found space + memset (new_cs, 0, sizeof(*new_cs)); + new_cs->size = size; + + new_cs->next = cs; + new_cs->prev = cs->prev; + cs->prev->next = new_cs; + cs->prev = new_cs; + + Cache_MakeLRU (new_cs); + + return new_cs; + } + } + + // continue looking + new_cs = (cache_system_t *)((byte *)cs + cs->size); + cs = cs->next; + + } while (cs != &cache_head); + +// try to allocate one at the very end + if ( hunk_base + hunk_size - hunk_high_used - (byte *)new_cs >= size) + { + memset (new_cs, 0, sizeof(*new_cs)); + new_cs->size = size; + + new_cs->next = &cache_head; + new_cs->prev = cache_head.prev; + cache_head.prev->next = new_cs; + cache_head.prev = new_cs; + + Cache_MakeLRU (new_cs); + + return new_cs; + } + + return NULL; // couldn't allocate +} + +/* +============ +Cache_Flush + +Throw everything out, so new data will be demand cached +============ +*/ +void Cache_Flush (void) +{ + while (cache_head.next != &cache_head) + Cache_Free ( cache_head.next->user, true); // reclaim the space //johnfitz -- added second argument +} + +/* +============ +Cache_Print + +============ +*/ +void Cache_Print (void) +{ + cache_system_t *cd; + + for (cd = cache_head.next ; cd != &cache_head ; cd = cd->next) + { + Con_Printf ("%8i : %s\n", cd->size, cd->name); + } +} + +/* +============ +Cache_Report + +============ +*/ +void Cache_Report (void) +{ + Con_DPrintf ("%4.1f megabyte data cache\n", (hunk_size - hunk_high_used - hunk_low_used) / (float)(1024*1024) ); +} + +/* +============ +Cache_Init + +============ +*/ +void Cache_Init (void) +{ + cache_head.next = cache_head.prev = &cache_head; + cache_head.lru_next = cache_head.lru_prev = &cache_head; + + Cmd_AddCommand ("flush", Cache_Flush); +} + +/* +============== +Cache_Free + +Frees the memory and removes it from the LRU list +============== +*/ +void Cache_Free (cache_user_t *c, qboolean freetextures) //johnfitz -- added second argument +{ + cache_system_t *cs; + + if (!c->data) + Sys_Error ("Cache_Free: not allocated"); + + cs = ((cache_system_t *)c->data) - 1; + + cs->prev->next = cs->next; + cs->next->prev = cs->prev; + cs->next = cs->prev = NULL; + + c->data = NULL; + + Cache_UnlinkLRU (cs); + + //johnfitz -- if a model becomes uncached, free the gltextures. This only works + //becuase the cache_user_t is the last component of the qmodel_t struct. Should + //fail harmlessly if *c is actually part of an sfx_t struct. I FEEL DIRTY + if (freetextures) + TexMgr_FreeTexturesForOwner ((qmodel_t *)(c + 1) - 1); +} + + + +/* +============== +Cache_Check +============== +*/ +void *Cache_Check (cache_user_t *c) +{ + cache_system_t *cs; + + if (!c->data) + return NULL; + + cs = ((cache_system_t *)c->data) - 1; + +// move to head of LRU + Cache_UnlinkLRU (cs); + Cache_MakeLRU (cs); + + return c->data; +} + + +/* +============== +Cache_Alloc +============== +*/ +void *Cache_Alloc (cache_user_t *c, int size, const char *name) +{ + cache_system_t *cs; + + if (c->data) + Sys_Error ("Cache_Alloc: already allocated"); + + if (size <= 0) + Sys_Error ("Cache_Alloc: size %i", size); + + size = (size + sizeof(cache_system_t) + 15) & ~15; + +// find memory for it + while (1) + { + cs = Cache_TryAlloc (size, false); + if (cs) + { + q_strlcpy (cs->name, name, CACHENAME_LEN); + c->data = (void *)(cs+1); + cs->user = c; + break; + } + + // free the least recently used cahedat + if (cache_head.lru_prev == &cache_head) + Sys_Error ("Cache_Alloc: out of memory"); // not enough memory at all + + Cache_Free (cache_head.lru_prev->user, true); //johnfitz -- added second argument + } + + return Cache_Check (c); +} + +//============================================================================ + + +static void Memory_InitZone (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; + + block->prev = block->next = &zone->blocklist; + block->tag = 0; // free block + block->id = ZONEID; + block->size = size - sizeof(memzone_t); +} + +/* +======================== +Memory_Init +======================== +*/ +void Memory_Init (void *buf, int size) +{ + int p; + int zonesize = DYNAMIC_SIZE; + + hunk_base = (byte *) buf; + hunk_size = size; + hunk_low_used = 0; + hunk_high_used = 0; + + Cache_Init (); + p = COM_CheckParm ("-zone"); + if (p) + { + if (p < com_argc-1) + zonesize = Q_atoi (com_argv[p+1]) * 1024; + else + Sys_Error ("Memory_Init: you must specify a size in KB after -zone"); + } + mainzone = (memzone_t *) Hunk_AllocName (zonesize, "zone" ); + Memory_InitZone (mainzone, zonesize); + + Cmd_AddCommand ("hunk_print", Hunk_Print_f); //johnfitz +} + diff --git a/Quake/zone.hpp b/Quake/zone.hpp new file mode 100644 index 0000000..74da9c7 --- /dev/null +++ b/Quake/zone.hpp @@ -0,0 +1,140 @@ +/* +Copyright (C) 1996-2001 Id Software, Inc. +Copyright (C) 2002-2009 John Fitzgibbons and others +Copyright (C) 2010-2014 QuakeSpasm developers + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __ZZONE_H +#define __ZZONE_H + +/* + memory allocation + + +H_??? The hunk manages the entire memory block given to quake. It must be +contiguous. Memory can be allocated from either the low or high end in a +stack fashion. The only way memory is released is by resetting one of the +pointers. + +Hunk allocations should be given a name, so the Hunk_Print () function +can display usage. + +Hunk allocations are guaranteed to be 16 byte aligned. + +The video buffers are allocated high to avoid leaving a hole underneath +server allocations when changing to a higher video mode. + + +Z_??? Zone memory functions used for small, dynamic allocations like text +strings from command input. There is only about 48K for it, allocated at +the very bottom of the hunk. + +Cache_??? Cache memory is for objects that can be dynamically loaded and +can usefully stay persistant between levels. The size of the cache +fluctuates from level to level. + +To allocate a cachable object + + +Temp_??? Temp memory is used for file loading and surface caching. The size +of the cache memory is adjusted so that there is a minimum of 512k remaining +for temp memory. + + +------ Top of Memory ------- + +high hunk allocations + +<--- high hunk reset point held by vid + +video buffer + +z buffer + +surface cache + +<--- high hunk used + +cachable memory + +<--- low hunk used + +client and server low hunk allocations + +<-- low hunk reset point held by host + +startup hunk allocations + +Zone block + +----- Bottom of Memory ----- + + + +*/ + +void Memory_Init (void *buf, int size); + +#ifdef __cplusplus +extern "C" { +#endif +void Z_Free (void *ptr); +void *Z_Malloc (int size); // returns 0 filled memory +void *Z_Realloc (void *ptr, int size); +char *Z_Strdup (const char *s); +#ifdef __cplusplus +} +#endif + +void *Hunk_Alloc (int size); // returns 0 filled memory +void *Hunk_AllocName (int size, const char *name); +void *Hunk_HighAllocName (int size, const char *name); +char *Hunk_Strdup (const char *s, const char *name); + +int Hunk_LowMark (void); +void Hunk_FreeToLowMark (int mark); + +int Hunk_HighMark (void); +void Hunk_FreeToHighMark (int mark); + +void *Hunk_TempAlloc (int size); + +void Hunk_Check (void); + +typedef struct cache_user_s +{ + void *data; +} cache_user_t; + +void Cache_Flush (void); + +void *Cache_Check (cache_user_t *c); +// returns the cached data, and moves to the head of the LRU list +// if present, otherwise returns NULL + +void Cache_Free (cache_user_t *c, qboolean freetextures); //johnfitz -- added second argument + +void *Cache_Alloc (cache_user_t *c, int size, const char *name); +// Returns NULL if all purgable data was tossed and there still +// wasn't enough room. + +void Cache_Report (void); + +#endif /* __ZZONE_H */ +